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 {
2022-10-07 00:54:33 +04:00
this . seoService . setTitle ( $localize ` :@@9d3ad4c6623870d96b65fb7a708fed6ce7c20044:Lightning Nodes Per Country ` ) ;
2023-08-30 20:26:07 +09:00
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 ;
2022-07-18 00:06:48 +02:00
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 ) ;
} ) ,
2022-07-18 00:06:48 +02:00
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 : ( ) = > {
2023-03-13 17:08:27 +09:00
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> ` +
2023-03-13 17:08:27 +09:00
$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 ,
2023-03-13 17:08:27 +09:00
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 : ( ) = > {
2023-03-13 17:08:27 +09:00
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 ) ;
}
}