mempool/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts

238 lines
6.9 KiB
TypeScript
Raw Normal View History

2022-07-17 11:10:17 +02:00
import { ChangeDetectionStrategy, Component, OnInit, HostBinding, NgZone } from '@angular/core';
import { Router } from '@angular/router';
2023-11-06 18:19:54 +00:00
import { EChartsOption, PieSeriesOption } from '../../graphs/echarts';
2022-07-17 11:10:17 +02:00
import { map, Observable, share, tap } from 'rxjs';
2022-09-21 17:23:45 +02:00
import { chartColors } from '../../app.constants';
import { ApiService } from '../../services/api.service';
import { SeoService } from '../../services/seo.service';
import { StateService } from '../../services/state.service';
import { download } from '../../shared/graphs.utils';
import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe';
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
import { getFlagEmoji } from '../../shared/common.utils';
2022-07-17 11:10:17 +02:00
@Component({
selector: 'app-nodes-per-country-chart',
templateUrl: './nodes-per-country-chart.component.html',
styleUrls: ['./nodes-per-country-chart.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NodesPerCountryChartComponent implements OnInit {
miningWindowPreference: string;
isLoading = true;
chartOptions: EChartsOption = {};
chartInitOptions = {
renderer: 'svg',
};
timespan = '';
chartInstance: any = undefined;
@HostBinding('attr.dir') dir = 'ltr';
nodesPerCountryObservable$: Observable<any>;
constructor(
private apiService: ApiService,
private seoService: SeoService,
private amountShortenerPipe: AmountShortenerPipe,
private zone: NgZone,
2023-11-02 01:29:55 +00:00
public stateService: StateService,
2022-07-17 11:10:17 +02:00
private router: Router,
) {
}
ngOnInit(): void {
this.seoService.setTitle($localize`:@@9d3ad4c6623870d96b65fb7a708fed6ce7c20044:Lightning Nodes Per Country`);
this.seoService.setDescription($localize`:@@meta.description.lightning.nodes-country-overview:See a geographical breakdown of the Lightning network: how many Lightning nodes are hosted in countries around the world, aggregate BTC capacity for each country, and more.`);
2022-09-06 19:33:07 +02:00
this.nodesPerCountryObservable$ = this.apiService.getNodesPerCountry$()
2022-07-17 11:10:17 +02:00
.pipe(
map(data => {
for (let i = 0; i < data.length; ++i) {
data[i].rank = i + 1;
data[i].iso = data[i].iso.toLowerCase();
2022-07-18 00:55:47 +02:00
data[i].flag = getFlagEmoji(data[i].iso);
2022-07-17 11:10:17 +02:00
}
return data.slice(0, 100);
}),
tap(data => {
this.isLoading = false;
this.prepareChartOptions(data);
}),
2022-07-17 11:10:17 +02:00
share()
);
}
generateChartSerieData(country) {
const shareThreshold = this.isMobile() ? 2 : 1;
const data: object[] = [];
let totalShareOther = 0;
let totalNodeOther = 0;
let edgeDistance: string | number = '10%';
if (this.isMobile()) {
edgeDistance = 0;
}
country.forEach((country) => {
if (country.share < shareThreshold) {
totalShareOther += country.share;
totalNodeOther += country.count;
return;
}
data.push({
value: country.share,
name: country.name.en + (this.isMobile() ? `` : ` (${country.share}%)`),
label: {
overflow: 'truncate',
color: '#b1b1b1',
alignTo: 'edge',
edgeDistance: edgeDistance,
},
tooltip: {
show: !this.isMobile(),
backgroundColor: 'rgba(17, 19, 31, 1)',
borderRadius: 4,
shadowColor: 'rgba(0, 0, 0, 0.5)',
textStyle: {
color: '#b1b1b1',
},
borderColor: '#000',
formatter: () => {
const nodeCount = country.count.toString();
2022-07-17 11:10:17 +02:00
return `<b style="color: white">${country.name.en} (${country.share}%)</b><br>` +
$localize`${nodeCount} nodes` + `<br>` +
2022-07-17 11:10:17 +02:00
$localize`${this.amountShortenerPipe.transform(country.capacity / 100000000, 2)} BTC capacity`
;
}
},
data: country.iso,
} as PieSeriesOption);
});
// 'Other'
data.push({
itemStyle: {
color: 'grey',
},
value: totalShareOther,
name: $localize`Other (${totalShareOther.toFixed(2) + '%'})`,
2022-07-17 11:10:17 +02:00
label: {
overflow: 'truncate',
color: '#b1b1b1',
alignTo: 'edge',
edgeDistance: edgeDistance
},
tooltip: {
backgroundColor: 'rgba(17, 19, 31, 1)',
borderRadius: 4,
shadowColor: 'rgba(0, 0, 0, 0.5)',
textStyle: {
color: '#b1b1b1',
},
borderColor: '#000',
formatter: () => {
const nodeCount = totalNodeOther.toString();
return `<b style="color: white">` + $localize`Other (${totalShareOther.toFixed(2) + '%'})` + `</b><br>` +
$localize`${nodeCount} nodes`;
2022-07-17 23:28:00 +02:00
},
2022-07-17 11:10:17 +02:00
},
2022-07-17 23:28:00 +02:00
data: 9999 as any
2022-07-17 11:10:17 +02:00
} as PieSeriesOption);
return data;
}
prepareChartOptions(country) {
let pieSize = ['20%', '80%']; // Desktop
if (this.isMobile()) {
pieSize = ['15%', '60%'];
}
this.chartOptions = {
animation: false,
color: chartColors,
tooltip: {
trigger: 'item',
textStyle: {
align: 'left',
}
},
series: [
{
zlevel: 0,
minShowLabelAngle: 3.6,
name: 'Mining pool',
type: 'pie',
radius: pieSize,
data: this.generateChartSerieData(country),
labelLine: {
lineStyle: {
width: 2,
},
length: this.isMobile() ? 1 : 20,
length2: this.isMobile() ? 1 : undefined,
},
label: {
fontSize: 14,
},
itemStyle: {
borderRadius: 1,
borderWidth: 1,
borderColor: '#000',
},
emphasis: {
itemStyle: {
shadowBlur: 40,
shadowColor: 'rgba(0, 0, 0, 0.75)',
},
labelLine: {
lineStyle: {
width: 4,
}
}
}
}
],
};
}
isMobile() {
return (window.innerWidth <= 767.98);
}
onChartInit(ec) {
if (this.chartInstance !== undefined) {
return;
}
this.chartInstance = ec;
this.chartInstance.on('click', (e) => {
if (e.data.data === 9999) { // "Other"
return;
}
this.zone.run(() => {
const url = new RelativeUrlPipe(this.stateService).transform(`/lightning/nodes/country/${e.data.data}`);
this.router.navigate([url]);
});
});
}
onSaveChart() {
const now = new Date();
this.chartOptions.backgroundColor = '#11131f';
this.chartInstance.setOption(this.chartOptions);
download(this.chartInstance.getDataURL({
pixelRatio: 2,
excludeComponents: ['dataZoom'],
}), `lightning-nodes-per-country-${Math.round(now.getTime() / 1000)}.svg`);
this.chartOptions.backgroundColor = 'none';
this.chartInstance.setOption(this.chartOptions);
}
isEllipsisActive(e) {
return (e.offsetWidth < e.scrollWidth);
}
}