Merge pull request #2519 from mempool/nymkappa/bugfix/show-hybrid-nodes-chart

Show tor+clearnet node series in chart
This commit is contained in:
wiz 2022-09-25 07:37:24 +09:00 committed by GitHub
commit fd8d61e742
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 126 additions and 79 deletions

View File

@ -6,7 +6,8 @@ class StatisticsApi {
public async $getStatistics(interval: string | null = null): Promise<any> { public async $getStatistics(interval: string | null = null): Promise<any> {
interval = Common.getSqlInterval(interval); interval = Common.getSqlInterval(interval);
let query = `SELECT UNIX_TIMESTAMP(added) AS added, channel_count, total_capacity, tor_nodes, clearnet_nodes, unannounced_nodes let query = `SELECT UNIX_TIMESTAMP(added) AS added, channel_count, total_capacity,
tor_nodes, clearnet_nodes, unannounced_nodes, clearnet_tor_nodes
FROM lightning_stats`; FROM lightning_stats`;
if (interval) { if (interval) {

View File

@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core'; import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core';
import { EChartsOption, graphic} from 'echarts'; import { EChartsOption, graphic, LineSeriesOption} from 'echarts';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { map, share, startWith, switchMap, tap } from 'rxjs/operators'; import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
import { formatNumber } from '@angular/common'; import { formatNumber } from '@angular/common';
@ -89,10 +89,11 @@ export class NodesNetworksChartComponent implements OnInit {
tor_nodes: data.map(val => [val.added * 1000, val.tor_nodes]), tor_nodes: data.map(val => [val.added * 1000, val.tor_nodes]),
clearnet_nodes: data.map(val => [val.added * 1000, val.clearnet_nodes]), clearnet_nodes: data.map(val => [val.added * 1000, val.clearnet_nodes]),
unannounced_nodes: data.map(val => [val.added * 1000, val.unannounced_nodes]), unannounced_nodes: data.map(val => [val.added * 1000, val.unannounced_nodes]),
clearnet_tor_nodes: data.map(val => [val.added * 1000, val.clearnet_tor_nodes]),
}; };
let maxYAxis = 0; let maxYAxis = 0;
for (const day of data) { for (const day of data) {
maxYAxis = Math.max(maxYAxis, day.tor_nodes + day.clearnet_nodes + day.unannounced_nodes); maxYAxis = Math.max(maxYAxis, day.tor_nodes + day.clearnet_nodes + day.unannounced_nodes + day.clearnet_tor_nodes);
} }
maxYAxis = Math.ceil(maxYAxis / 3000) * 3000; maxYAxis = Math.ceil(maxYAxis / 3000) * 3000;
this.prepareChartOptions(chartData, maxYAxis); this.prepareChartOptions(chartData, maxYAxis);
@ -134,6 +135,94 @@ export class NodesNetworksChartComponent implements OnInit {
}; };
} }
const series: LineSeriesOption[] = [
{
zlevel: 1,
yAxisIndex: 0,
name: $localize`Unknown`,
showSymbol: false,
symbol: 'none',
data: data.unannounced_nodes,
type: 'line',
lineStyle: {
width: 2,
},
areaStyle: {
opacity: 0.5,
},
stack: 'Total',
color: new graphic.LinearGradient(0, 0.75, 0, 1, [
{ offset: 0, color: '#D81B60' },
{ offset: 1, color: '#D81B60AA' },
]),
smooth: false,
},
{
zlevel: 1,
yAxisIndex: 0,
name: $localize`Reachable on Clearnet Only`,
showSymbol: false,
symbol: 'none',
data: data.clearnet_nodes,
type: 'line',
lineStyle: {
width: 2,
},
areaStyle: {
opacity: 0.5,
},
stack: 'Total',
color: new graphic.LinearGradient(0, 0.75, 0, 1, [
{ offset: 0, color: '#FFB300' },
{ offset: 1, color: '#FFB300AA' },
]),
smooth: false,
},
{
zlevel: 1,
yAxisIndex: 0,
name: $localize`Reachable on Clearnet and Darknet`,
showSymbol: false,
symbol: 'none',
data: data.clearnet_tor_nodes,
type: 'line',
lineStyle: {
width: 2,
},
areaStyle: {
opacity: 0.5,
},
stack: 'Total',
color: new graphic.LinearGradient(0, 0.75, 0, 1, [
{ offset: 0, color: '#be7d4c' },
{ offset: 1, color: '#be7d4cAA' },
]),
smooth: false,
},
{
zlevel: 1,
yAxisIndex: 0,
name: $localize`Reachable on Darknet Only`,
showSymbol: false,
symbol: 'none',
data: data.tor_nodes,
type: 'line',
lineStyle: {
width: 2,
},
areaStyle: {
opacity: 0.5,
},
stack: 'Total',
color: new graphic.LinearGradient(0, 0.75, 0, 1, [
{ offset: 0, color: '#7D4698' },
{ offset: 1, color: '#7D4698AA' },
]),
smooth: false,
},
];
this.chartOptions = { this.chartOptions = {
title: title, title: title,
animation: false, animation: false,
@ -164,12 +253,17 @@ export class NodesNetworksChartComponent implements OnInit {
let tooltip = `<b style="color: white; margin-left: 2px">${date}</b><br>`; let tooltip = `<b style="color: white; margin-left: 2px">${date}</b><br>`;
for (const tick of ticks.reverse()) { for (const tick of ticks.reverse()) {
if (tick.seriesName.indexOf('ignored') !== -1) {
continue;
}
if (tick.seriesIndex === 0) { // Tor if (tick.seriesIndex === 0) { // Tor
tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.0-0')}`; tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.0-0')}`;
} else if (tick.seriesIndex === 1) { // Clearnet } else if (tick.seriesIndex === 1) { // Clearnet
tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.0-0')}`; tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.0-0')}`;
} else if (tick.seriesIndex === 2) { // Unannounced } else if (tick.seriesIndex === 2) { // Unannounced
tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.0-0')}`; tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.0-0')}`;
} else if (tick.seriesIndex === 3) { // Tor + Clearnet
tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.0-0')}`;
} }
tooltip += `<br>`; tooltip += `<br>`;
total += tick.data[1]; total += tick.data[1];
@ -190,7 +284,7 @@ export class NodesNetworksChartComponent implements OnInit {
padding: 10, padding: 10,
data: [ data: [
{ {
name: $localize`Total`, name: $localize`Reachable on Darknet Only`,
inactiveColor: 'rgb(110, 112, 121)', inactiveColor: 'rgb(110, 112, 121)',
textStyle: { textStyle: {
color: 'white', color: 'white',
@ -198,7 +292,7 @@ export class NodesNetworksChartComponent implements OnInit {
icon: 'roundRect', icon: 'roundRect',
}, },
{ {
name: $localize`Tor`, name: $localize`Reachable on Clearnet and Darknet`,
inactiveColor: 'rgb(110, 112, 121)', inactiveColor: 'rgb(110, 112, 121)',
textStyle: { textStyle: {
color: 'white', color: 'white',
@ -206,7 +300,7 @@ export class NodesNetworksChartComponent implements OnInit {
icon: 'roundRect', icon: 'roundRect',
}, },
{ {
name: $localize`Clearnet`, name: $localize`Reachable on Clearnet Only`,
inactiveColor: 'rgb(110, 112, 121)', inactiveColor: 'rgb(110, 112, 121)',
textStyle: { textStyle: {
color: 'white', color: 'white',
@ -214,7 +308,7 @@ export class NodesNetworksChartComponent implements OnInit {
icon: 'roundRect', icon: 'roundRect',
}, },
{ {
name: $localize`Unannounced`, name: $localize`Unknown`,
inactiveColor: 'rgb(110, 112, 121)', inactiveColor: 'rgb(110, 112, 121)',
textStyle: { textStyle: {
color: 'white', color: 'white',
@ -223,10 +317,10 @@ export class NodesNetworksChartComponent implements OnInit {
}, },
], ],
selected: this.widget ? undefined : JSON.parse(this.storageService.getValue('nodes_networks_legend')) ?? { selected: this.widget ? undefined : JSON.parse(this.storageService.getValue('nodes_networks_legend')) ?? {
'Total': true, '$localize`Reachable on Darknet Only`': true,
'Tor': true, '$localize`Reachable on Clearnet Only`': true,
'Clearnet': true, '$localize`Reachable on Clearnet and Darknet`': true,
'Unannounced': true, '$localize`Unknown`': true,
} }
}, },
yAxis: data.tor_nodes.length === 0 ? undefined : [ yAxis: data.tor_nodes.length === 0 ? undefined : [
@ -250,7 +344,6 @@ export class NodesNetworksChartComponent implements OnInit {
opacity: 0.25, opacity: 0.25,
}, },
}, },
max: maxYAxis,
min: 0, min: 0,
interval: 3000, interval: 3000,
}, },
@ -274,77 +367,25 @@ export class NodesNetworksChartComponent implements OnInit {
opacity: 0.25, opacity: 0.25,
}, },
}, },
max: maxYAxis,
min: 0, min: 0,
interval: 3000, interval: 3000,
} }
], ],
series: data.tor_nodes.length === 0 ? [] : [ series: data.tor_nodes.length === 0 ? [] : series.concat(series.map((serie) => {
{ // We create dummy duplicated series so when we use the data zoom, the y axis
zlevel: 1, // both scales properly
yAxisIndex: 0, const invisibleSerie = {...serie};
name: $localize`Unannounced`, invisibleSerie.name = 'ignored' + Math.random().toString();
showSymbol: false, invisibleSerie.stack = 'ignored';
symbol: 'none', invisibleSerie.yAxisIndex = 1;
data: data.unannounced_nodes, invisibleSerie.lineStyle = {
type: 'line', opacity: 0,
lineStyle: { };
width: 2, invisibleSerie.areaStyle = {
}, opacity: 0,
areaStyle: { };
opacity: 0.5, return invisibleSerie;
}, })),
stack: 'Total',
color: new graphic.LinearGradient(0, 0.75, 0, 1, [
{ offset: 0, color: '#D81B60' },
{ offset: 1, color: '#D81B60AA' },
]),
smooth: false,
},
{
zlevel: 1,
yAxisIndex: 0,
name: $localize`Clearnet`,
showSymbol: false,
symbol: 'none',
data: data.clearnet_nodes,
type: 'line',
lineStyle: {
width: 2,
},
areaStyle: {
opacity: 0.5,
},
stack: 'Total',
color: new graphic.LinearGradient(0, 0.75, 0, 1, [
{ offset: 0, color: '#FFB300' },
{ offset: 1, color: '#FFB300AA' },
]),
smooth: false,
},
{
zlevel: 1,
yAxisIndex: 1,
name: $localize`Tor`,
showSymbol: false,
symbol: 'none',
data: data.tor_nodes,
type: 'line',
lineStyle: {
width: 2,
},
areaStyle: {
opacity: 0.5,
},
stack: 'Total',
color: new graphic.LinearGradient(0, 0.75, 0, 1, [
{ offset: 0, color: '#7D4698' },
{ offset: 1, color: '#7D4698AA' },
]),
smooth: false,
},
],
dataZoom: this.widget ? null : [{ dataZoom: this.widget ? null : [{
type: 'inside', type: 'inside',
realtime: true, realtime: true,
@ -371,6 +412,11 @@ export class NodesNetworksChartComponent implements OnInit {
}, },
}], }],
}; };
if (isMobile()) {
// @ts-ignore
this.chartOptions.legend.left = 50;
}
} }
onChartInit(ec): void { onChartInit(ec): void {