Create world map of clearnet LN nodes and channels
This commit is contained in:
		
							parent
							
								
									9a915b6bf4
								
							
						
					
					
						commit
						31ee96a3c7
					
				@ -13,6 +13,30 @@ class ChannelsApi {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $getAllChannelsGeo(): Promise<any[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const query = `SELECT nodes_1.public_key as node1_public_key, nodes_1.alias AS node1_alias,
 | 
			
		||||
        nodes_1.latitude AS node1_latitude, nodes_1.longitude AS node1_longitude,
 | 
			
		||||
        nodes_2.public_key as node2_public_key, nodes_2.alias AS node2_alias,
 | 
			
		||||
        nodes_2.latitude AS node2_latitude, nodes_2.longitude AS node2_longitude,
 | 
			
		||||
        channels.capacity
 | 
			
		||||
      FROM channels
 | 
			
		||||
      JOIN nodes AS nodes_1 on nodes_1.public_key = channels.node1_public_key
 | 
			
		||||
      JOIN nodes AS nodes_2 on nodes_2.public_key = channels.node2_public_key
 | 
			
		||||
      WHERE nodes_1.latitude IS NOT NULL AND nodes_1.longitude IS NOT NULL
 | 
			
		||||
        AND nodes_2.latitude IS NOT NULL AND nodes_2.longitude IS NOT NULL
 | 
			
		||||
      `;
 | 
			
		||||
      const [rows]: any = await DB.query(query);
 | 
			
		||||
      return rows.map((row) => [
 | 
			
		||||
        row.node1_public_key, row.node1_alias, row.node1_longitude, row.node1_latitude,
 | 
			
		||||
        row.node2_public_key, row.node2_alias, row.node2_longitude, row.node2_latitude,
 | 
			
		||||
        row.capacity]);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('$getAllChannels error: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $searchChannelsById(search: string): Promise<any[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const searchStripped = search.replace('%', '') + '%';
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@ class ChannelsRoutes {
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/channels/search/:search', this.$searchChannelsById)
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/channels/:short_id', this.$getChannel)
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/channels', this.$getChannelsForNode)
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/channels-geo', this.$getChannelsGeo)
 | 
			
		||||
    ;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -93,6 +94,15 @@ class ChannelsRoutes {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async $getChannelsGeo(req: Request, res: Response) {
 | 
			
		||||
    try {
 | 
			
		||||
      const channels = await channelsApi.$getAllChannelsGeo();
 | 
			
		||||
      res.json(channels);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      res.status(500).send(e instanceof Error ? e.message : e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default new ChannelsRoutes();
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										32
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										32
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							@ -34,6 +34,7 @@
 | 
			
		||||
        "clipboard": "^2.0.10",
 | 
			
		||||
        "domino": "^2.1.6",
 | 
			
		||||
        "echarts": "~5.3.2",
 | 
			
		||||
        "echarts-gl": "^2.0.9",
 | 
			
		||||
        "express": "^4.17.1",
 | 
			
		||||
        "lightweight-charts": "~3.8.0",
 | 
			
		||||
        "ngx-echarts": "8.0.1",
 | 
			
		||||
@ -6396,6 +6397,11 @@
 | 
			
		||||
        "webpack": ">=4.0.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/claygl": {
 | 
			
		||||
      "version": "1.3.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/claygl/-/claygl-1.3.0.tgz",
 | 
			
		||||
      "integrity": "sha512-+gGtJjT6SSHD2l2yC3MCubW/sCV40tZuSs5opdtn79vFSGUgp/lH139RNEQ6Jy078/L0aV8odCw8RSrUcMfLaQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/clean-stack": {
 | 
			
		||||
      "version": "2.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
 | 
			
		||||
@ -8107,6 +8113,18 @@
 | 
			
		||||
        "zrender": "5.3.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/echarts-gl": {
 | 
			
		||||
      "version": "2.0.9",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/echarts-gl/-/echarts-gl-2.0.9.tgz",
 | 
			
		||||
      "integrity": "sha512-oKeMdkkkpJGWOzjgZUsF41DOh6cMsyrGGXimbjK2l6Xeq/dBQu4ShG2w2Dzrs/1bD27b2pLTGSaUzouY191gzA==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "claygl": "^1.2.1",
 | 
			
		||||
        "zrender": "^5.1.1"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "echarts": "^5.1.2"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/echarts/node_modules/tslib": {
 | 
			
		||||
      "version": "2.3.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
 | 
			
		||||
@ -22520,6 +22538,11 @@
 | 
			
		||||
      "integrity": "sha512-g38K9Cm5WRwlaH6g03B9OEz/0qRizI+2I7n+Gz+L5DxXJAPAiWQvwlYNm1V1jkdpUv95bOe/ASm2vfi/G560jQ==",
 | 
			
		||||
      "requires": {}
 | 
			
		||||
    },
 | 
			
		||||
    "claygl": {
 | 
			
		||||
      "version": "1.3.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/claygl/-/claygl-1.3.0.tgz",
 | 
			
		||||
      "integrity": "sha512-+gGtJjT6SSHD2l2yC3MCubW/sCV40tZuSs5opdtn79vFSGUgp/lH139RNEQ6Jy078/L0aV8odCw8RSrUcMfLaQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "clean-stack": {
 | 
			
		||||
      "version": "2.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
 | 
			
		||||
@ -23866,6 +23889,15 @@
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "echarts-gl": {
 | 
			
		||||
      "version": "2.0.9",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/echarts-gl/-/echarts-gl-2.0.9.tgz",
 | 
			
		||||
      "integrity": "sha512-oKeMdkkkpJGWOzjgZUsF41DOh6cMsyrGGXimbjK2l6Xeq/dBQu4ShG2w2Dzrs/1bD27b2pLTGSaUzouY191gzA==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "claygl": "^1.2.1",
 | 
			
		||||
        "zrender": "^5.1.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "ee-first": {
 | 
			
		||||
      "version": "1.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
 | 
			
		||||
 | 
			
		||||
@ -88,6 +88,7 @@
 | 
			
		||||
    "clipboard": "^2.0.10",
 | 
			
		||||
    "domino": "^2.1.6",
 | 
			
		||||
    "echarts": "~5.3.2",
 | 
			
		||||
    "echarts-gl": "^2.0.9",
 | 
			
		||||
    "express": "^4.17.1",
 | 
			
		||||
    "lightweight-charts": "~3.8.0",
 | 
			
		||||
    "ngx-echarts": "8.0.1",
 | 
			
		||||
 | 
			
		||||
@ -39,7 +39,9 @@
 | 
			
		||||
      <a class="dropdown-item" routerLinkActive="active" [routerLink]="['/graphs/lightning/nodes-per-country' | relativeUrl]"
 | 
			
		||||
        i18n="lightning.nodes-per-isp">Lightning nodes per country</a>
 | 
			
		||||
      <a class="dropdown-item" routerLinkActive="active" [routerLink]="['/graphs/lightning/nodes-map' | relativeUrl]"
 | 
			
		||||
        i18n="lightning.nodes-per-isp">Lightning nodes world map</a>
 | 
			
		||||
        i18n="lightning.lightning.nodes-heatmap">Lightning nodes world map</a>
 | 
			
		||||
      <a class="dropdown-item" routerLinkActive="active" [routerLink]="['/graphs/lightning/nodes-channels-map' | relativeUrl]"
 | 
			
		||||
        i18n="lightning.nodes-channels-world-map">Lightning nodes channels world map</a>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@ -23,6 +23,7 @@ import { LightningStatisticsChartComponent } from '../lightning/statistics-chart
 | 
			
		||||
import { NodesPerISPChartComponent } from '../lightning/nodes-per-isp-chart/nodes-per-isp-chart.component';
 | 
			
		||||
import { NodesPerCountryChartComponent } from '../lightning/nodes-per-country-chart/nodes-per-country-chart.component';
 | 
			
		||||
import { NodesMap } from '../lightning/nodes-map/nodes-map.component';
 | 
			
		||||
import { NodesChannelsMap } from '../lightning/nodes-channels-map/nodes-channels-map.component';
 | 
			
		||||
 | 
			
		||||
const browserWindow = window || {};
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
@ -114,6 +115,10 @@ const routes: Routes = [
 | 
			
		||||
            path: 'lightning/nodes-map',
 | 
			
		||||
            component: NodesMap,
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            path: 'lightning/nodes-channels-map',
 | 
			
		||||
            component: NodesChannelsMap,
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            path: '',
 | 
			
		||||
            redirectTo: 'mempool',
 | 
			
		||||
 | 
			
		||||
@ -23,6 +23,7 @@ import { NodesPerCountry } from './nodes-per-country/nodes-per-country.component
 | 
			
		||||
import { NodesPerISP } from './nodes-per-isp/nodes-per-isp.component';
 | 
			
		||||
import { NodesPerCountryChartComponent } from '../lightning/nodes-per-country-chart/nodes-per-country-chart.component';
 | 
			
		||||
import { NodesMap } from '../lightning/nodes-map/nodes-map.component';
 | 
			
		||||
import { NodesChannelsMap } from '../lightning/nodes-channels-map/nodes-channels-map.component';
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [
 | 
			
		||||
    LightningDashboardComponent,
 | 
			
		||||
@ -43,6 +44,7 @@ import { NodesMap } from '../lightning/nodes-map/nodes-map.component';
 | 
			
		||||
    NodesPerISP,
 | 
			
		||||
    NodesPerCountryChartComponent,
 | 
			
		||||
    NodesMap,
 | 
			
		||||
    NodesChannelsMap,
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    CommonModule,
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,17 @@
 | 
			
		||||
<div class="full-container">
 | 
			
		||||
 | 
			
		||||
  <div class="card-header">
 | 
			
		||||
    <div class="d-flex d-md-block align-items-baseline" style="margin-bottom: -5px">
 | 
			
		||||
      <span i18n="lightning.nodes-channels-world-map">Lightning nodes channels world map</span>
 | 
			
		||||
      <button class="btn p-0 pl-2" style="margin: 0 0 4px 0px">
 | 
			
		||||
        <fa-icon [icon]="['fas', 'download']" [fixedWidth]="true" (click)="onSaveChart()"></fa-icon>
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <small style="color: #ffffff66" i18n="lightning.tor-nodes-excluded">(Tor nodes excluded)</small>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <div *ngIf="observable$ | async" class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
 | 
			
		||||
    (chartInit)="onChartInit($event)">
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
@ -0,0 +1,40 @@
 | 
			
		||||
.card-header {
 | 
			
		||||
  border-bottom: 0;
 | 
			
		||||
  font-size: 18px;
 | 
			
		||||
  @media (min-width: 465px) {
 | 
			
		||||
    font-size: 20px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.full-container {
 | 
			
		||||
  padding: 0px 15px;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  min-height: 500px;
 | 
			
		||||
  height: calc(100% - 150px);
 | 
			
		||||
  @media (max-width: 992px) {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    padding-bottom: 100px;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chart {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  padding-bottom: 20px;
 | 
			
		||||
  padding-right: 10px;
 | 
			
		||||
  @media (max-width: 992px) {
 | 
			
		||||
    padding-bottom: 25px;
 | 
			
		||||
  }
 | 
			
		||||
  @media (max-width: 829px) {
 | 
			
		||||
    padding-bottom: 50px;
 | 
			
		||||
  }
 | 
			
		||||
  @media (max-width: 767px) {
 | 
			
		||||
    padding-bottom: 25px;
 | 
			
		||||
  }
 | 
			
		||||
  @media (max-width: 629px) {
 | 
			
		||||
    padding-bottom: 55px;
 | 
			
		||||
  }
 | 
			
		||||
  @media (max-width: 567px) {
 | 
			
		||||
    padding-bottom: 55px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,190 @@
 | 
			
		||||
import { ChangeDetectionStrategy, Component, NgZone, OnDestroy, OnInit } 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 { AssetsService } from 'src/app/services/assets.service';
 | 
			
		||||
import { download } from 'src/app/shared/graphs.utils';
 | 
			
		||||
import { Router } from '@angular/router';
 | 
			
		||||
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { EChartsOption, registerMap } from 'echarts';
 | 
			
		||||
import 'echarts-gl';
 | 
			
		||||
import { SSL_OP_SSLEAY_080_CLIENT_DH_BUG } from 'constants';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-nodes-channels-map',
 | 
			
		||||
  templateUrl: './nodes-channels-map.component.html',
 | 
			
		||||
  styleUrls: ['./nodes-channels-map.component.scss'],
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
export class NodesChannelsMap implements OnInit, OnDestroy {
 | 
			
		||||
  observable$: Observable<any>;
 | 
			
		||||
 | 
			
		||||
  chartInstance = undefined;
 | 
			
		||||
  chartOptions: EChartsOption = {color: 'dark'};
 | 
			
		||||
  chartInitOptions = {
 | 
			
		||||
    renderer: 'canvas',
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private seoService: SeoService,
 | 
			
		||||
    private apiService: ApiService,
 | 
			
		||||
    private stateService: StateService,
 | 
			
		||||
    private assetsService: AssetsService,
 | 
			
		||||
    private router: Router,
 | 
			
		||||
    private zone: NgZone,
 | 
			
		||||
  ) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy(): void {}
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.seoService.setTitle($localize`Lightning nodes channels world map`);
 | 
			
		||||
 | 
			
		||||
    this.observable$ = zip(
 | 
			
		||||
      this.assetsService.getWorldMapJson$,
 | 
			
		||||
      this.apiService.getChannelsGeo$(),
 | 
			
		||||
    ).pipe(tap((data) => {
 | 
			
		||||
      registerMap('world', data[0]);
 | 
			
		||||
 | 
			
		||||
      const channelsLoc = [];
 | 
			
		||||
      const nodes = [];
 | 
			
		||||
      for (const channel of data[1]) {
 | 
			
		||||
        channelsLoc.push([[channel[2], channel[3]], [channel[6], channel[7]]]);
 | 
			
		||||
        nodes.push({
 | 
			
		||||
          publicKey: channel[0],
 | 
			
		||||
          name: channel[1],
 | 
			
		||||
          value: [channel[2], channel[3]],
 | 
			
		||||
        });
 | 
			
		||||
        nodes.push({
 | 
			
		||||
          publicKey: channel[4],
 | 
			
		||||
          name: channel[5],
 | 
			
		||||
          value: [channel[6], channel[7]],
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this.prepareChartOptions(nodes, channelsLoc);
 | 
			
		||||
    }));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  prepareChartOptions(nodes, channels) {
 | 
			
		||||
    let title: object;
 | 
			
		||||
    if (channels.length === 0) {
 | 
			
		||||
      title = {
 | 
			
		||||
        textStyle: {
 | 
			
		||||
          color: 'grey',
 | 
			
		||||
          fontSize: 15
 | 
			
		||||
        },
 | 
			
		||||
        text: $localize`No data to display yet`,
 | 
			
		||||
        left: 'center',
 | 
			
		||||
        top: 'center'
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.chartOptions = {
 | 
			
		||||
      geo3D: {
 | 
			
		||||
        map: 'world',
 | 
			
		||||
        shading: 'color',
 | 
			
		||||
        silent: true,
 | 
			
		||||
        environment: '#000',
 | 
			
		||||
        postEffect: {
 | 
			
		||||
          enable: true,
 | 
			
		||||
          bloom: {
 | 
			
		||||
            intensity: 0.01,
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        viewControl: {
 | 
			
		||||
          minDistance: 1,
 | 
			
		||||
          distance: 60,
 | 
			
		||||
          alpha: 89,
 | 
			
		||||
          panMouseButton: 'left',
 | 
			
		||||
          rotateMouseButton: 'right',
 | 
			
		||||
          zoomSensivity: 0.5,
 | 
			
		||||
        },
 | 
			
		||||
        itemStyle: {
 | 
			
		||||
          color: '#FFFFFF',
 | 
			
		||||
          opacity: 0.02,
 | 
			
		||||
          borderWidth: 1,
 | 
			
		||||
          borderColor: 'black',
 | 
			
		||||
        },
 | 
			
		||||
        regionHeight: 0.01,
 | 
			
		||||
      },
 | 
			
		||||
      series: [
 | 
			
		||||
        {
 | 
			
		||||
          // @ts-ignore
 | 
			
		||||
          type: 'lines3D',
 | 
			
		||||
          coordinateSystem: 'geo3D',
 | 
			
		||||
          blendMode: 'lighter',
 | 
			
		||||
          lineStyle: {
 | 
			
		||||
            width: 1,
 | 
			
		||||
            opacity: 0.025,
 | 
			
		||||
          },
 | 
			
		||||
          data: channels
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          // @ts-ignore
 | 
			
		||||
          type: 'scatter3D',
 | 
			
		||||
          symbol: 'circle',
 | 
			
		||||
          blendMode: 'lighter',
 | 
			
		||||
          coordinateSystem: 'geo3D',
 | 
			
		||||
          symbolSize: 3,
 | 
			
		||||
          itemStyle: {
 | 
			
		||||
            color: '#BBFFFF',
 | 
			
		||||
            opacity: 1,
 | 
			
		||||
            borderColor: '#FFFFFF00',
 | 
			
		||||
          },
 | 
			
		||||
          data: nodes,
 | 
			
		||||
          emphasis: {
 | 
			
		||||
            label: {
 | 
			
		||||
              position: 'top',
 | 
			
		||||
              // @ts-ignore
 | 
			
		||||
              textStyle: {
 | 
			
		||||
                color: 'white',
 | 
			
		||||
                fontSize: 16,
 | 
			
		||||
              },
 | 
			
		||||
              formatter: function(value) {
 | 
			
		||||
                return value.name;
 | 
			
		||||
              },
 | 
			
		||||
              show: true,
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
      ]
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onChartInit(ec) {
 | 
			
		||||
    if (this.chartInstance !== undefined) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.chartInstance = ec;
 | 
			
		||||
 | 
			
		||||
    this.chartInstance.on('click', (e) => {
 | 
			
		||||
      if (e.data && e.data.publicKey) {
 | 
			
		||||
        this.zone.run(() => {
 | 
			
		||||
          const url = new RelativeUrlPipe(this.stateService).transform(`/lightning/node/${e.data.publicKey}`);
 | 
			
		||||
          this.router.navigate([url]);
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onSaveChart() {
 | 
			
		||||
    // @ts-ignore
 | 
			
		||||
    const prevBottom = this.chartOptions.grid.bottom;
 | 
			
		||||
    const now = new Date();
 | 
			
		||||
    // @ts-ignore
 | 
			
		||||
    this.chartOptions.grid.bottom = 30;
 | 
			
		||||
    this.chartOptions.backgroundColor = '#11131f';
 | 
			
		||||
    this.chartInstance.setOption(this.chartOptions);
 | 
			
		||||
    download(this.chartInstance.getDataURL({
 | 
			
		||||
      pixelRatio: 2,
 | 
			
		||||
      excludeComponents: ['dataZoom'],
 | 
			
		||||
    }), `lightning-nodes-heatmap-clearnet-${Math.round(now.getTime() / 1000)}.svg`);
 | 
			
		||||
    // @ts-ignore
 | 
			
		||||
    this.chartOptions.grid.bottom = prevBottom;
 | 
			
		||||
    this.chartOptions.backgroundColor = 'none';
 | 
			
		||||
    this.chartInstance.setOption(this.chartOptions);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -4,7 +4,7 @@ import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { combineLatest, Observable, tap } from 'rxjs';
 | 
			
		||||
import { AssetsService } from 'src/app/services/assets.service';
 | 
			
		||||
import { EChartsOption, MapSeriesOption, registerMap } from 'echarts';
 | 
			
		||||
import { EChartsOption, registerMap } from 'echarts';
 | 
			
		||||
import { download } from 'src/app/shared/graphs.utils';
 | 
			
		||||
import { Router } from '@angular/router';
 | 
			
		||||
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
 | 
			
		||||
 | 
			
		||||
@ -266,4 +266,8 @@ export class ApiService {
 | 
			
		||||
  getNodesPerCountry(): Observable<any> {
 | 
			
		||||
    return this.httpClient.get<any[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/countries');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getChannelsGeo$(): Observable<any> {
 | 
			
		||||
    return this.httpClient.get<any[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/channels-geo');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user