Merge branch 'master' into nofollow-api-links

This commit is contained in:
wiz
2022-08-11 20:09:12 +09:00
committed by GitHub
38 changed files with 1103 additions and 488 deletions

View File

@@ -76,10 +76,8 @@
</div>
<div [class]="!widget ? 'bottom-padding' : 'pb-0'" class="container pb-lg-0">
<div>
<div [class]="widget ? 'chart-widget' : 'chart'" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)">
</div>
<div [class]="widget ? 'chart-widget' : 'chart'" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)">
</div>
<div class="text-center loadingGraphs" *ngIf="isLoading">

View File

@@ -14,7 +14,9 @@
<div class="clearfix"></div>
<div class="box">
<app-nodes-channels-map *ngIf="!error" [style]="'channelpage'" [channel]="channelGeo"></app-nodes-channels-map>
<div class="box">
<div class="row">
<div class="col-md">

View File

@@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Observable, of } from 'rxjs';
import { catchError, switchMap } from 'rxjs/operators';
import { catchError, switchMap, tap } from 'rxjs/operators';
import { SeoService } from 'src/app/services/seo.service';
import { LightningApiService } from '../lightning-api.service';
@@ -14,6 +14,7 @@ import { LightningApiService } from '../lightning-api.service';
export class ChannelComponent implements OnInit {
channel$: Observable<any>;
error: any = null;
channelGeo: number[] = [];
constructor(
private lightningApiService: LightningApiService,
@@ -29,9 +30,23 @@ export class ChannelComponent implements OnInit {
this.seoService.setTitle(`Channel: ${params.get('short_id')}`);
return this.lightningApiService.getChannel$(params.get('short_id'))
.pipe(
tap((data) => {
if (!data.node_left.longitude || !data.node_left.latitude ||
!data.node_right.longitude || !data.node_right.latitude) {
this.channelGeo = [];
} else {
this.channelGeo = [
data.node_left.public_key,
data.node_left.alias,
data.node_left.longitude, data.node_left.latitude,
data.node_right.public_key,
data.node_right.alias,
data.node_right.longitude, data.node_right.latitude,
];
}
}),
catchError((err) => {
this.error = err;
console.log(this.error);
return of(null);
})
);

View File

@@ -4,6 +4,7 @@
<div class="row row-cols-1 row-cols-md-2">
<!-- Network capacity/channels/nodes -->
<div class="col">
<div class="main-title">
<span i18n="lightning.statistics-title">Network Statistics</span>&nbsp;
@@ -17,6 +18,7 @@
</div>
</div>
<!-- Channels stats -->
<div class="col">
<div class="main-title">
<span i18n="lightning.statistics-title">Channels Statistics</span>&nbsp;
@@ -30,18 +32,28 @@
</div>
</div>
<div class="col">
<!-- ISP pie chart -->
<div class="col" style="margin-bottom: 1.47rem">
<div class="card graph-card">
<div class="card-body pl-2 pr-2">
<app-nodes-per-isp-chart [widget]="true"></app-nodes-per-isp-chart>
<div class="mt-1"><a [attr.data-cy]="'pool-distribution-view-more'" [routerLink]="['/graphs/lightning/nodes-per-isp' | relativeUrl]" i18n="dashboard.view-more">View more &raquo;</a></div>
</div>
</div>
</div>
<!-- <div class="col">
<div class="card">
<div class="card-body">
<app-nodes-networks-chart [widget]=true></app-nodes-networks-chart>
<div class="mt-1"><a [routerLink]="['/graphs/lightning/nodes-networks' | relativeUrl]" i18n="dashboard.view-more">View more &raquo;</a></div>
</div>
</div>
</div>
</div> -->
<div class="col">
<div class="card">
<div class="card-body">
<div class="card graph-card">
<div class="card-body pl-2 pr-2">
<app-lightning-statistics-chart [widget]=true></app-lightning-statistics-chart>
<div class="mt-1"><a [routerLink]="['/graphs/lightning/capacity' | relativeUrl]" i18n="dashboard.view-more">View more &raquo;</a></div>
</div>
@@ -52,7 +64,7 @@
<div class="card">
<div class="card-body">
<h5 class="card-title">Top Capacity Nodes</h5>
<app-nodes-list [nodes$]="nodesByCapacity$"></app-nodes-list>
<app-nodes-list [nodes$]="nodesByCapacity$" [show]="'mobile-capacity'"></app-nodes-list>
<!-- <div><a [routerLink]="['/lightning/nodes' | relativeUrl]" i18n="dashboard.view-more">View more &raquo;</a></div> -->
</div>
</div>
@@ -62,7 +74,7 @@
<div class="card">
<div class="card-body">
<h5 class="card-title">Most Connected Nodes</h5>
<app-nodes-list [nodes$]="nodesByChannels$"></app-nodes-list>
<app-nodes-list [nodes$]="nodesByChannels$" [show]="'mobile-channels'"></app-nodes-list>
<!-- <div><a [routerLink]="['/lightning/nodes' | relativeUrl]" i18n="dashboard.view-more">View more &raquo;</a></div> -->
</div>
</div>

View File

@@ -14,6 +14,13 @@
background-color: #1d1f31;
}
.graph-card {
height: 100%;
@media (min-width: 992px) {
height: 385px;
}
}
.card-title {
font-size: 1rem;
color: #4a68b9;
@@ -22,9 +29,6 @@
color: #4a68b9;
}
.card-body {
padding: 1.25rem 1rem 0.75rem 1rem;
}
.card-body.pool-ranking {
padding: 1.25rem 0.25rem 0.75rem 0.25rem;
}
@@ -32,6 +36,21 @@
font-size: 22px;
}
#blockchain-container {
position: relative;
overflow-x: scroll;
overflow-y: hidden;
scrollbar-width: none;
-ms-overflow-style: none;
}
#blockchain-container::-webkit-scrollbar {
display: none;
}
.fade-border {
-webkit-mask-image: linear-gradient(to right, transparent 0%, black 10%, black 80%, transparent 100%)
}
.main-title {
position: relative;
@@ -45,7 +64,7 @@
}
.more-padding {
padding: 18px;
padding: 24px 20px !important;
}
.card-wrapper {
@@ -78,3 +97,10 @@
.card-text {
font-size: 22px;
}
.title-link, .title-link:hover, .title-link:focus, .title-link:active {
display: block;
margin-bottom: 10px;
text-decoration: none;
color: inherit;
}

View File

@@ -30,21 +30,28 @@
}
.widget {
width: 99vw;
width: 90vw;
margin-left: auto;
margin-right: auto;
height: 250px;
-webkit-mask: linear-gradient(0deg, #11131f00 5%, #11131fff 25%);
@media (max-width: 767.98px) {
width: 100vw;
}
}
.widget > .chart {
-webkit-mask: linear-gradient(180deg, #11131f00 0%, #11131fff 20%);
min-height: 250px;
-webkit-mask: linear-gradient(180deg, #11131f00 0%, #11131fff 20%);
@media (max-width: 767.98px) {
padding-bottom: 0px;
}
}
.chart {
min-height: 500px;
width: 100%;
height: 100%;
padding-right: 10px;
@media (max-width: 992px) {
padding-bottom: 25px;
}

View File

@@ -8,6 +8,7 @@ import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.
import { StateService } from 'src/app/services/state.service';
import { EChartsOption, registerMap } from 'echarts';
import 'echarts-gl';
import { isMobile } from 'src/app/shared/common.utils';
@Component({
selector: 'app-nodes-channels-map',
@@ -16,11 +17,18 @@ import 'echarts-gl';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NodesChannelsMap implements OnInit, OnDestroy {
@Input() style: 'graph' | 'nodepage' | 'widget' = 'graph';
@Input() style: 'graph' | 'nodepage' | 'widget' | 'channelpage' = 'graph';
@Input() publicKey: string | undefined;
@Input() channel: any[] = [];
observable$: Observable<any>;
center: number[] | undefined = undefined;
center: number[] | undefined;
zoom: number | undefined;
channelWidth = 0.6;
channelOpacity = 0.1;
channelColor = '#466d9d';
channelCurve = 0;
chartInstance = undefined;
chartOptions: EChartsOption = {};
@@ -42,8 +50,16 @@ export class NodesChannelsMap implements OnInit, OnDestroy {
ngOnDestroy(): void {}
ngOnInit(): void {
this.center = this.style === 'widget' ? [0, 0, -10] : undefined;
this.center = this.style === 'widget' ? [0, 40] : [0, 5];
this.zoom = 1.3;
if (this.style === 'widget' && !isMobile()) {
this.zoom = 3.5;
}
if (this.style === 'widget' && isMobile()) {
this.zoom = 1.4;
this.center = [0, 10];
}
if (this.style === 'graph') {
this.seoService.setTitle($localize`Lightning nodes channels world map`);
}
@@ -62,36 +78,85 @@ export class NodesChannelsMap implements OnInit, OnDestroy {
const nodes = [];
const nodesPubkeys = {};
let thisNodeGPS: number[] | undefined = undefined;
for (const channel of data[1]) {
let geoloc = data[1];
if (this.style === 'channelpage') {
if (this.channel.length === 0) {
geoloc = [];
} else {
geoloc = [this.channel];
}
}
for (const channel of geoloc) {
if (!thisNodeGPS && data[2] === channel[0]) {
thisNodeGPS = [channel[2], channel[3]];
} else if (!thisNodeGPS && data[2] === channel[4]) {
thisNodeGPS = [channel[6], channel[7]];
}
channelsLoc.push([[channel[2], channel[3]], [channel[6], channel[7]]]);
// 0 - node1 pubkey
// 1 - node1 alias
// 2,3 - node1 GPS
// 4 - node2 pubkey
// 5 - node2 alias
// 6,7 - node2 GPS
// We add a bit of noise so nodes at the same location are not all
// on top of each other
let random = Math.random() * 2 * Math.PI;
let random2 = Math.random() * 0.01;
if (!nodesPubkeys[channel[0]]) {
nodes.push({
publicKey: channel[0],
name: channel[1],
value: [channel[2], channel[3]],
});
nodesPubkeys[channel[0]] = true;
nodes.push([
channel[2] + random2 * Math.cos(random),
channel[3] + random2 * Math.sin(random),
1,
channel[0],
channel[1]
]);
nodesPubkeys[channel[0]] = nodes[nodes.length - 1];
}
random = Math.random() * 2 * Math.PI;
random2 = Math.random() * 0.01;
if (!nodesPubkeys[channel[4]]) {
nodes.push({
publicKey: channel[4],
name: channel[5],
value: [channel[6], channel[7]],
});
nodesPubkeys[channel[4]] = true;
nodes.push([
channel[6] + random2 * Math.cos(random),
channel[7] + random2 * Math.sin(random),
1,
channel[4],
channel[5]
]);
nodesPubkeys[channel[4]] = nodes[nodes.length - 1];
}
const channelLoc = [];
channelLoc.push(nodesPubkeys[channel[0]].slice(0, 2));
channelLoc.push(nodesPubkeys[channel[4]].slice(0, 2));
channelsLoc.push(channelLoc);
}
if (this.style === 'nodepage' && thisNodeGPS) {
// 1ML 0217890e3aad8d35bc054f43acc00084b25229ecff0ab68debd82883ad65ee8266
// New York GPS [-74.0068, 40.7123]
// Map center [-20.55, 0, -9.85]
this.center = [thisNodeGPS[0] * -20.55 / -74.0068, 0, thisNodeGPS[1] * -9.85 / 40.7123];
this.center = [thisNodeGPS[0], thisNodeGPS[1]];
this.zoom = 10;
this.channelWidth = 1;
this.channelOpacity = 1;
}
if (this.style === 'channelpage' && this.channel.length > 0) {
this.channelWidth = 2;
this.channelOpacity = 1;
this.channelColor = '#bafcff';
this.channelCurve = 0.1;
this.center = [
(this.channel[2] + this.channel[6]) / 2,
(this.channel[3] + this.channel[7]) / 2
];
const distance = Math.sqrt(
Math.pow(this.channel[7] - this.channel[3], 2) +
Math.pow(this.channel[6] - this.channel[2], 2)
);
this.zoom = -0.05 * distance + 8;
}
this.prepareChartOptions(nodes, channelsLoc);
@@ -115,87 +180,83 @@ export class NodesChannelsMap implements OnInit, OnDestroy {
}
this.chartOptions = {
silent: this.style === 'widget' ? true : false,
silent: this.style === 'widget',
title: title ?? undefined,
geo3D: {
map: 'world',
shading: 'color',
tooltip: {},
geo: {
animation: false,
silent: true,
postEffect: {
enable: true,
bloom: {
intensity: 0.1,
}
},
viewControl: {
center: this.center,
minDistance: 1,
maxDistance: 60,
distance: this.style === 'widget' ? 22 : this.style === 'nodepage' ? 22 : 60,
alpha: 90,
rotateSensitivity: 0,
panSensitivity: this.style === 'widget' ? 0 : 1,
zoomSensitivity: this.style === 'widget' ? 0 : 0.5,
panMouseButton: this.style === 'widget' ? null : 'left',
rotateMouseButton: undefined,
center: this.center,
zoom: this.zoom,
tooltip: {
show: true
},
map: 'world',
roam: this.style === 'widget' ? false : true,
itemStyle: {
color: 'white',
opacity: 0.02,
borderWidth: 1,
borderColor: 'black',
color: '#ffffff44'
},
regionHeight: 0.01,
scaleLimit: {
min: 1.3,
max: 100000,
}
},
series: [
{
// @ts-ignore
type: 'lines3D',
coordinateSystem: 'geo3D',
blendMode: 'lighter',
lineStyle: {
width: 1,
opacity: ['widget', 'graph'].includes(this.style) ? 0.025 : 1,
large: true,
type: 'scatter',
data: nodes,
coordinateSystem: 'geo',
geoIndex: 0,
symbolSize: 4,
tooltip: {
backgroundColor: 'rgba(17, 19, 31, 1)',
borderRadius: 4,
shadowColor: 'rgba(0, 0, 0, 0.5)',
textStyle: {
color: '#b1b1b1',
align: 'left',
},
borderColor: '#000',
formatter: (value) => {
const data = value.data;
const alias = data[4].length > 0 ? data[4] : data[3].slice(0, 20);
return `<b style="color: white">${alias}</b>`;
}
},
data: channels
itemStyle: {
color: 'white',
borderColor: 'black',
borderWidth: 2,
opacity: 1,
},
blendMode: 'lighter',
zlevel: 1,
},
{
// @ts-ignore
type: 'scatter3D',
symbol: 'circle',
blendMode: 'lighter',
coordinateSystem: 'geo3D',
symbolSize: 3,
itemStyle: {
color: '#BBFFFF',
opacity: 1,
borderColor: '#FFFFFF00',
large: false,
progressive: 200,
silent: true,
type: 'lines',
coordinateSystem: 'geo',
data: channels,
lineStyle: {
opacity: this.channelOpacity,
width: this.channelWidth,
curveness: this.channelCurve,
color: this.channelColor,
},
data: nodes,
emphasis: {
label: {
position: 'top',
color: 'white',
fontSize: 16,
formatter: function(value) {
return value.name;
},
show: true,
}
}
},
blendMode: 'lighter',
tooltip: {
show: false,
},
zlevel: 2,
}
]
};
}
@HostListener('window:wheel', ['$event'])
onWindowScroll(e): void {
// Not very smooth when using the mouse
if (this.style === 'widget' && e.target.tagName === 'CANVAS') {
window.scrollBy({left: 0, top: e.deltaY, behavior: 'auto'});
}
}
onChartInit(ec) {
if (this.chartInstance !== undefined) {
return;
@@ -211,14 +272,34 @@ export class NodesChannelsMap implements OnInit, OnDestroy {
});
});
}
this.chartInstance.on('click', (e) => {
if (e.data && e.data.publicKey) {
if (e.data) {
this.zone.run(() => {
const url = new RelativeUrlPipe(this.stateService).transform(`/lightning/node/${e.data.publicKey}`);
const url = new RelativeUrlPipe(this.stateService).transform(`/lightning/node/${e.data[3]}`);
this.router.navigate([url]);
});
}
});
this.chartInstance.on('georoam', (e) => {
if (!e.zoom || this.style === 'nodepage') {
return;
}
const speed = 0.005;
const chartOptions = {
series: this.chartOptions.series
};
chartOptions.series[1].lineStyle.opacity += e.zoom > 1 ? speed : -speed;
chartOptions.series[1].lineStyle.width += e.zoom > 1 ? speed : -speed;
chartOptions.series[0].symbolSize += e.zoom > 1 ? speed * 10 : -speed * 10;
chartOptions.series[1].lineStyle.opacity = Math.max(0.05, Math.min(0.5, chartOptions.series[1].lineStyle.opacity));
chartOptions.series[1].lineStyle.width = Math.max(0.5, Math.min(1, chartOptions.series[1].lineStyle.width));
chartOptions.series[0].symbolSize = Math.max(4, Math.min(5.5, chartOptions.series[0].symbolSize));
this.chartInstance.setOption(chartOptions);
});
}
}

View File

@@ -3,18 +3,18 @@
<table class="table table-borderless">
<thead>
<th class="alias text-left" i18n="nodes.alias">Alias</th>
<th class="capacity text-right" i18n="node.capacity">Capacity</th>
<th class="channels text-right" i18n="node.channels">Channels</th>
<th class="capacity text-right" [class]="show" i18n="node.capacity">Capacity</th>
<th class="channels text-right" [class]="show" i18n="node.channels">Channels</th>
</thead>
<tbody *ngIf="nodes$ | async as nodes; else skeleton">
<tr *ngFor="let node of nodes; let i = index;">
<td class="alias text-left">
<a [routerLink]="['/lightning/node' | relativeUrl, node.public_key]">{{ node.alias }}</a>
</td>
<td class="capacity text-right">
<td class="capacity text-right" [class]="show">
<app-amount [satoshis]="node.capacity" digitsInfo="1.2-2"></app-amount>
</td>
<td class="channels text-right">
<td class="channels text-right" [class]="show">
{{ node.channels | number }}
</td>
</tr>

View File

@@ -0,0 +1,11 @@
.capacity.mobile-channels {
@media (max-width: 767.98px) {
display: none;
}
}
.channels.mobile-capacity {
@media (max-width: 767.98px) {
display: none;
}
}

View File

@@ -9,6 +9,7 @@ import { Observable } from 'rxjs';
})
export class NodesListComponent implements OnInit {
@Input() nodes$: Observable<any>;
@Input() show: string;
constructor() { }

View File

@@ -1,6 +1,29 @@
<div class="full-container h-100">
<div [class]="widget === false ? 'full-container' : ''">
<div class="card-header">
<div *ngIf="widget">
<div class="pool-distribution" *ngIf="(nodesPerAsObservable$ | async) as stats; else loadingReward">
<div class="item">
<h5 class="card-title d-inline-block" i18n="lightning.tagged-isp">Tagged ISPs</h5>
<p class="card-text">
{{ stats.taggedISP }}
</p>
</div>
<div class="item">
<h5 class="card-title d-inline-block" i18n="lightning.tagged-capacity">Tagged capacity</h5>
<p class="card-text" i18n-ngbTooltip="mining.blocks-count-desc">
<app-amount [satoshis]="stats.taggedCapacity" [digitsInfo]="'1.2-2'" [noFiat]="true"></app-amount>
</p>
</div>
<div class="item">
<h5 class="card-title d-inline-block" i18n="lightning.tagged-nodes">Tagged nodes</h5>
<p class="card-text" i18n-ngbTooltip="mining.pools-count-desc">
{{ stats.taggedNodeCount }}
</p>
</div>
</div>
</div>
<div class="card-header" *ngIf="!widget">
<div class="d-flex d-md-block align-items-baseline" style="margin-bottom: -5px">
<span i18n="lightning.nodes-per-isp">Lightning nodes per ISP</span>
<button class="btn p-0 pl-2" style="margin: 0 0 4px 0px" (click)="onSaveChart()">
@@ -12,23 +35,21 @@
</small>
</div>
<div class="container pb-lg-0 bottom-padding">
<div class="pb-lg-5" *ngIf="nodesPerAsObservable$ | async">
<div class="chart w-100" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)">
</div>
<div [class]="!widget ? 'bottom-padding' : 'pb-0'" class="container pb-lg-0">
<div [class]="widget ? 'chart-widget' : 'chart'" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)">
</div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="spinner-border text-light"></div>
</div>
<div class="d-flex toggle">
<div class="d-flex toggle" *ngIf="!widget">
<app-toggle [textLeft]="'Show Tor'" [textRight]="" (toggleStatusChanged)="onTorToggleStatusChanged($event)"></app-toggle>
<app-toggle [textLeft]="'Nodes'" [textRight]="'Capacity'" (toggleStatusChanged)="onGroupToggleStatusChanged($event)"></app-toggle>
</div>
<table class="table table-borderless text-center m-auto" style="max-width: 900px">
<table class="table table-borderless text-center m-auto" style="max-width: 900px" *ngIf="!widget">
<thead>
<tr>
<th class="rank text-left pl-0" i18n="mining.rank">Rank</th>
@@ -39,7 +60,7 @@
</tr>
</thead>
<tbody [attr.data-cy]="'pools-table'" *ngIf="(nodesPerAsObservable$ | async) as asList">
<tr *ngFor="let asEntry of asList">
<tr *ngFor="let asEntry of asList.data">
<td class="rank text-left pl-0">{{ asEntry.rank }}</td>
<td class="name text-left text-truncate">
<a *ngIf="asEntry.ispId" [routerLink]="[('/lightning/nodes/isp/' + asEntry.ispId) | relativeUrl]">{{ asEntry.name }}</a>
@@ -54,3 +75,26 @@
</div>
</div>
<ng-template #loadingReward>
<div class="pool-distribution">
<div class="item">
<h5 class="card-title" i18n="lightning.tagged-isp">Tagged ISPs</h5>
<p class="card-text">
<span class="skeleton-loader skeleton-loader-big"></span>
</p>
</div>
<div class="item">
<h5 class="card-title" i18n="lightning.tagged-capacity">Tagged capacity</h5>
<p class="card-text">
<span class="skeleton-loader skeleton-loader-big"></span>
</p>
</div>
<div class="item">
<h5 class="card-title" i18n="lightning.tagged-nodes">Tagged nodes</h5>
<p class="card-text">
<span class="skeleton-loader skeleton-loader-big"></span>
</p>
</div>
</div>
</ng-template>

View File

@@ -22,7 +22,40 @@
max-height: 400px;
@media (max-width: 767.98px) {
max-height: 230px;
margin-top: -35px;
margin-top: -40px;
}
}
.chart-widget {
width: 100%;
height: 100%;
height: 240px;
@media (max-width: 485px) {
max-height: 200px;
}
}
.formRadioGroup {
margin-top: 6px;
display: flex;
flex-direction: column;
@media (min-width: 991px) {
position: relative;
top: -65px;
}
@media (min-width: 830px) and (max-width: 991px) {
position: relative;
top: 0px;
}
@media (min-width: 830px) {
flex-direction: row;
float: right;
margin-top: 0px;
}
.btn-sm {
font-size: 9px;
@media (min-width: 830px) {
font-size: 14px;
}
}
}
@@ -35,6 +68,79 @@
};
}
@media (max-width: 767.98px) {
.pools-table th,
.pools-table td {
padding: .3em !important;
}
}
.loadingGraphs {
position: absolute;
top: 50%;
left: calc(50% - 15px);
z-index: 100;
}
.pool-distribution {
min-height: 56px;
display: block;
@media (min-width: 485px) {
display: flex;
flex-direction: row;
}
h5 {
margin-bottom: 5px;
}
.item {
max-width: 160px;
width: 50%;
display: inline-block;
margin: 0px auto 20px;
&:nth-child(2) {
order: 2;
@media (min-width: 485px) {
order: 3;
}
}
&:nth-child(3) {
width: 50%;
order: 3;
@media (min-width: 485px) {
order: 2;
display: block;
}
@media (min-width: 768px) {
display: none;
}
@media (min-width: 992px) {
display: block;
}
}
.card-title {
font-size: 1rem;
color: #4a68b9;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card-text {
font-size: 18px;
span {
color: #ffffff66;
font-size: 12px;
}
}
}
}
.skeleton-loader {
width: 100%;
display: block;
max-width: 80px;
margin: 15px auto 3px;
}
.rank {
width: 15%;
@media (max-width: 576px) {

View File

@@ -1,11 +1,12 @@
import { ChangeDetectionStrategy, Component, OnInit, HostBinding, NgZone } from '@angular/core';
import { ChangeDetectionStrategy, Component, OnInit, HostBinding, NgZone, Input } from '@angular/core';
import { Router } from '@angular/router';
import { EChartsOption, PieSeriesOption } from 'echarts';
import { combineLatest, map, Observable, share, Subject, switchMap, tap } from 'rxjs';
import { combineLatest, map, Observable, share, startWith, Subject, switchMap, tap } from 'rxjs';
import { chartColors } from 'src/app/app.constants';
import { ApiService } from 'src/app/services/api.service';
import { SeoService } from 'src/app/services/seo.service';
import { StateService } from 'src/app/services/state.service';
import { isMobile } from 'src/app/shared/common.utils';
import { download } from 'src/app/shared/graphs.utils';
import { AmountShortenerPipe } from 'src/app/shared/pipes/amount-shortener.pipe';
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
@@ -17,6 +18,8 @@ import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NodesPerISPChartComponent implements OnInit {
@Input() widget: boolean = false;
isLoading = true;
chartOptions: EChartsOption = {};
chartInitOptions = {
@@ -46,7 +49,11 @@ export class NodesPerISPChartComponent implements OnInit {
this.seoService.setTitle($localize`Lightning nodes per ISP`);
this.showTorObservable$ = this.showTorSubject.asObservable();
this.nodesPerAsObservable$ = combineLatest([this.groupBySubject, this.showTorSubject])
this.nodesPerAsObservable$ = combineLatest([
this.groupBySubject.pipe(startWith(false)),
this.showTorSubject.pipe(startWith(false)),
])
.pipe(
switchMap((selectedFilters) => {
return this.apiService.getNodesPerAs(
@@ -62,23 +69,41 @@ export class NodesPerISPChartComponent implements OnInit {
for (let i = 0; i < data.length; ++i) {
data[i].rank = i + 1;
}
return data.slice(0, 100);
return {
taggedISP: data.length,
taggedCapacity: data.reduce((partialSum, isp) => partialSum + isp.capacity, 0),
taggedNodeCount: data.reduce((partialSum, isp) => partialSum + isp.count, 0),
data: data.slice(0, 100),
};
})
);
}),
share()
);
if (this.widget) {
this.showTorSubject.next(false);
this.groupBySubject.next(false);
}
}
generateChartSerieData(as): PieSeriesOption[] {
const shareThreshold = this.isMobile() ? 2 : 0.5;
let shareThreshold = 0.5;
if (this.widget && isMobile() || isMobile()) {
shareThreshold = 1;
} else if (this.widget) {
shareThreshold = 0.75;
}
const data: object[] = [];
let totalShareOther = 0;
let totalNodeOther = 0;
let edgeDistance: string | number = '10%';
if (this.isMobile()) {
if (isMobile() && this.widget) {
edgeDistance = 0;
} else if (isMobile() && !this.widget || this.widget) {
edgeDistance = 10;
}
as.forEach((as) => {
@@ -92,15 +117,16 @@ export class NodesPerISPChartComponent implements OnInit {
color: as.ispId === null ? '#7D4698' : undefined,
},
value: as.share,
name: as.name + (this.isMobile() ? `` : ` (${as.share}%)`),
name: as.name + (isMobile() || this.widget ? `` : ` (${as.share}%)`),
label: {
overflow: 'truncate',
width: isMobile() ? 75 : this.widget ? 125 : 250,
color: '#b1b1b1',
alignTo: 'edge',
edgeDistance: edgeDistance,
},
tooltip: {
show: !this.isMobile(),
show: !isMobile(),
backgroundColor: 'rgba(17, 19, 31, 1)',
borderRadius: 4,
shadowColor: 'rgba(0, 0, 0, 0.5)',
@@ -125,7 +151,7 @@ export class NodesPerISPChartComponent implements OnInit {
color: 'grey',
},
value: totalShareOther,
name: 'Other' + (this.isMobile() ? `` : ` (${totalShareOther.toFixed(2)}%)`),
name: 'Other' + (isMobile() || this.widget ? `` : ` (${totalShareOther.toFixed(2)}%)`),
label: {
overflow: 'truncate',
color: '#b1b1b1',
@@ -153,7 +179,7 @@ export class NodesPerISPChartComponent implements OnInit {
prepareChartOptions(as): void {
let pieSize = ['20%', '80%']; // Desktop
if (this.isMobile()) {
if (isMobile() && !this.widget) {
pieSize = ['15%', '60%'];
}
@@ -177,8 +203,8 @@ export class NodesPerISPChartComponent implements OnInit {
lineStyle: {
width: 2,
},
length: this.isMobile() ? 1 : 20,
length2: this.isMobile() ? 1 : undefined,
length: isMobile() ? 1 : 20,
length2: isMobile() ? 1 : undefined,
},
label: {
fontSize: 14,
@@ -204,10 +230,6 @@ export class NodesPerISPChartComponent implements OnInit {
};
}
isMobile(): boolean {
return (window.innerWidth <= 767.98);
}
onChartInit(ec): void {
if (this.chartInstance !== undefined) {
return;
@@ -244,5 +266,9 @@ export class NodesPerISPChartComponent implements OnInit {
onGroupToggleStatusChanged(e): void {
this.groupBySubject.next(e);
}
isEllipsisActive(e) {
return (e.offsetWidth < e.scrollWidth);
}
}

View File

@@ -51,8 +51,7 @@
}
.chart-widget {
width: 100%;
height: 100%;
max-height: 270px;
height: 320px;
}
.formRadioGroup {