diff --git a/frontend/src/app/lightning/nodes-map/nodes-map.component.scss b/frontend/src/app/lightning/nodes-map/nodes-map.component.scss
index d7ad42b46..7a67e4eb4 100644
--- a/frontend/src/app/lightning/nodes-map/nodes-map.component.scss
+++ b/frontend/src/app/lightning/nodes-map/nodes-map.component.scss
@@ -21,6 +21,17 @@
height: 240px;
padding: 0px;
}
+.full-container.fit-container {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ min-height: 100px;
+
+ .chart {
+ padding: 0;
+ min-height: 100px;
+ }
+}
.chart {
width: 100%;
diff --git a/frontend/src/app/lightning/nodes-map/nodes-map.component.ts b/frontend/src/app/lightning/nodes-map/nodes-map.component.ts
index b783e225a..8ec853aaa 100644
--- a/frontend/src/app/lightning/nodes-map/nodes-map.component.ts
+++ b/frontend/src/app/lightning/nodes-map/nodes-map.component.ts
@@ -1,7 +1,7 @@
-import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, NgZone, OnDestroy, OnInit } from '@angular/core';
+import { ChangeDetectionStrategy, Component, Inject, Input, Output, EventEmitter, LOCALE_ID, NgZone, OnDestroy, OnInit, OnChanges } from '@angular/core';
import { SeoService } from 'src/app/services/seo.service';
import { ApiService } from 'src/app/services/api.service';
-import { Observable, tap, zip } from 'rxjs';
+import { Observable, BehaviorSubject, switchMap, tap, combineLatest } from 'rxjs';
import { AssetsService } from 'src/app/services/assets.service';
import { EChartsOption, registerMap } from 'echarts';
import { lerpColor } from 'src/app/shared/graphs.utils';
@@ -17,11 +17,14 @@ import { getFlagEmoji } from 'src/app/shared/common.utils';
styleUrls: ['./nodes-map.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class NodesMap implements OnInit {
+export class NodesMap implements OnInit, OnChanges {
@Input() widget: boolean = false;
@Input() nodes: any[] | undefined = undefined;
@Input() type: 'none' | 'isp' | 'country' = 'none';
-
+ @Input() fitContainer = false;
+ @Output() readyEvent = new EventEmitter();
+ inputNodes$: BehaviorSubject
;
+ nodes$: Observable;
observable$: Observable;
chartInstance = undefined;
@@ -45,9 +48,17 @@ export class NodesMap implements OnInit {
ngOnInit(): void {
this.seoService.setTitle($localize`Lightning nodes world map`);
- this.observable$ = zip(
+ if (!this.inputNodes$) {
+ this.inputNodes$ = new BehaviorSubject(this.nodes);
+ }
+
+ this.nodes$ = this.inputNodes$.pipe(
+ switchMap((nodes) => nodes ? [nodes] : this.apiService.getWorldNodes$())
+ );
+
+ this.observable$ = combineLatest(
this.assetsService.getWorldMapJson$,
- this.nodes ? [this.nodes] : this.apiService.getWorldNodes$()
+ this.nodes$
).pipe(tap((data) => {
registerMap('world', data[0]);
@@ -110,6 +121,16 @@ export class NodesMap implements OnInit {
}));
}
+ ngOnChanges(changes): void {
+ if (changes.nodes) {
+ if (!this.inputNodes$) {
+ this.inputNodes$ = new BehaviorSubject(changes.nodes.currentValue);
+ } else {
+ this.inputNodes$.next(changes.nodes.currentValue);
+ }
+ }
+ }
+
prepareChartOptions(nodes, maxLiquidity, mapCenter, mapZoom) {
let title: object;
if (nodes.length === 0) {
@@ -224,4 +245,8 @@ export class NodesMap implements OnInit {
this.chartInstance.resize();
});
}
+
+ onChartFinished(e) {
+ this.readyEvent.emit();
+ }
}
diff --git a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html
new file mode 100644
index 000000000..4db69156f
--- /dev/null
+++ b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html
@@ -0,0 +1,63 @@
+
+
+ lightning ISP
+
+
+
+
+
+
+
+
+ Nodes |
+ {{ ispNodes.nodes.length }} |
+
+
+ Liquidity |
+
+ 100000000; else smallnode" [satoshis]="ispNodes.sumLiquidity" [digitsInfo]="'1.2-2'" [noFiat]="false">
+
+
+
+ |
+
+
+ Channels |
+ {{ ispNodes.sumChannels }} |
+
+
+ Top country |
+
+ {{ ispNodes.topCountry.country }} {{ ispNodes.topCountry.flag }}
+ |
+
+
+ Top node |
+
+ {{ ispNodes.nodes[0].alias }}
+ |
+
+
+
+
+
+
+
+
+
+
+ Error loading data.
+
+
diff --git a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.scss b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.scss
new file mode 100644
index 000000000..2fe34ef5e
--- /dev/null
+++ b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.scss
@@ -0,0 +1,31 @@
+.table {
+ font-size: 32px;
+ margin-top: 0px;
+}
+
+.map-col {
+ flex-grow: 0;
+ flex-shrink: 0;
+ width: 470px;
+ height: 360px;
+ min-width: 470px;
+ min-height: 360px;
+ max-height: 360px;
+ padding: 0;
+ background: #181b2d;
+ overflow: hidden;
+ margin-top: 0;
+}
+
+.row {
+ margin-right: 0;
+}
+
+.full-width-row {
+ padding-left: 15px;
+ flex-wrap: nowrap;
+}
+
+::ng-deep .symbol {
+ font-size: 24px;
+}
\ No newline at end of file
diff --git a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.ts b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.ts
new file mode 100644
index 000000000..18e2f2d6c
--- /dev/null
+++ b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.ts
@@ -0,0 +1,103 @@
+import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { ActivatedRoute, ParamMap } from '@angular/router';
+import { catchError, map, switchMap, Observable, share, of } from 'rxjs';
+import { ApiService } from 'src/app/services/api.service';
+import { SeoService } from 'src/app/services/seo.service';
+import { OpenGraphService } from 'src/app/services/opengraph.service';
+import { getFlagEmoji } from 'src/app/shared/common.utils';
+import { GeolocationData } from 'src/app/shared/components/geolocation/geolocation.component';
+
+@Component({
+ selector: 'app-nodes-per-isp-preview',
+ templateUrl: './nodes-per-isp-preview.component.html',
+ styleUrls: ['./nodes-per-isp-preview.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class NodesPerISPPreview implements OnInit {
+ nodes$: Observable;
+ isp: {name: string, id: number};
+ id: string;
+ error: Error;
+
+ constructor(
+ private apiService: ApiService,
+ private seoService: SeoService,
+ private openGraphService: OpenGraphService,
+ private route: ActivatedRoute,
+ ) { }
+
+ ngOnInit(): void {
+ this.nodes$ = this.route.paramMap
+ .pipe(
+ switchMap((params: ParamMap) => {
+ this.id = params.get('isp');
+ this.isp = null;
+ this.openGraphService.waitFor('isp-map-' + this.id);
+ this.openGraphService.waitFor('isp-data-' + this.id);
+ return this.apiService.getNodeForISP$(params.get('isp'));
+ }),
+ map(response => {
+ this.isp = {
+ name: response.isp,
+ id: this.route.snapshot.params.isp.split(',').join(', ')
+ };
+ this.seoService.setTitle($localize`Lightning nodes on ISP: ${response.isp} [AS${this.route.snapshot.params.isp}]`);
+
+ for (const i in response.nodes) {
+ response.nodes[i].geolocation = {
+ country: response.nodes[i].country?.en,
+ city: response.nodes[i].city?.en,
+ subdivision: response.nodes[i].subdivision?.en,
+ iso: response.nodes[i].iso_code,
+ };
+ }
+
+ const sumLiquidity = response.nodes.reduce((partialSum, a) => partialSum + a.capacity, 0);
+ const sumChannels = response.nodes.reduce((partialSum, a) => partialSum + a.channels, 0);
+ const countries = {};
+ const topCountry = {
+ count: 0,
+ country: '',
+ iso: '',
+ flag: '',
+ };
+ for (const node of response.nodes) {
+ if (!node.geolocation.iso) {
+ continue;
+ }
+ countries[node.geolocation.iso] = countries[node.geolocation.iso] ?? 0 + 1;
+ if (countries[node.geolocation.iso] > topCountry.count) {
+ topCountry.count = countries[node.geolocation.iso];
+ topCountry.country = node.geolocation.country;
+ topCountry.iso = node.geolocation.iso;
+ }
+ }
+ topCountry.flag = getFlagEmoji(topCountry.iso);
+
+ this.openGraphService.waitOver('isp-data-' + this.id);
+
+ return {
+ nodes: response.nodes,
+ sumLiquidity: sumLiquidity,
+ sumChannels: sumChannels,
+ topCountry: topCountry,
+ };
+ }),
+ catchError(err => {
+ this.error = err;
+ this.openGraphService.fail('isp-map-' + this.id);
+ this.openGraphService.fail('isp-data-' + this.id);
+ return of({
+ nodes: [],
+ sumLiquidity: 0,
+ sumChannels: 0,
+ topCountry: {},
+ });
+ })
+ );
+ }
+
+ onMapReady() {
+ this.openGraphService.waitOver('isp-map-' + this.id);
+ }
+}
diff --git a/unfurler/src/routes.ts b/unfurler/src/routes.ts
index c6e77e79a..4c25bf93b 100644
--- a/unfurler/src/routes.ts
+++ b/unfurler/src/routes.ts
@@ -46,6 +46,17 @@ const routes = {
return `Lightning Channel: ${path[0]}`;
}
},
+ nodes: {
+ routes: {
+ isp: {
+ render: true,
+ params: 1,
+ getTitle(path) {
+ return `Lightning ISP: ${path[0]}`;
+ }
+ }
+ }
+ }
}
},
mining: {