Add new component incoming-transactions-graph;
Refactor component mempool-graph; Refactor component fee-distribution-graph; Add incoming-transactions-graph to dashboard; Add incoming-transactions-graph to statistics; Add incoming-transactions-graph to television; Add mempool-graph to dashboard; Add mempool-graph to statistics; Add mempool-graph to television; Remove chartist.component;
This commit is contained in:
		
							parent
							
								
									4772cc4517
								
							
						
					
					
						commit
						c5831cc4e8
					
				
							
								
								
									
										14
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										14
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							@ -24,7 +24,6 @@
 | 
			
		||||
        "@fortawesome/fontawesome-svg-core": "^1.2.35",
 | 
			
		||||
        "@fortawesome/free-solid-svg-icons": "^5.15.3",
 | 
			
		||||
        "@juggle/resize-observer": "^3.3.1",
 | 
			
		||||
        "@mempool/chartist": "^0.11.4",
 | 
			
		||||
        "@mempool/mempool.js": "^2.2.4",
 | 
			
		||||
        "@ng-bootstrap/ng-bootstrap": "^7.0.0",
 | 
			
		||||
        "@nguniversal/express-engine": "11.2.1",
 | 
			
		||||
@ -2260,14 +2259,6 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.3.1.tgz",
 | 
			
		||||
      "integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mempool/chartist": {
 | 
			
		||||
      "version": "0.11.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@mempool/chartist/-/chartist-0.11.4.tgz",
 | 
			
		||||
      "integrity": "sha512-wSemsw2NIWS7/SHxjDe9upSdUETxNRebY0ByaJzcONKUzJSUzMuSNmKEdD3kr/g02H++JvsXR2znLC6tYEAbPA==",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=4.6.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mempool/mempool.js": {
 | 
			
		||||
      "version": "2.2.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@mempool/mempool.js/-/mempool.js-2.2.4.tgz",
 | 
			
		||||
@ -21966,11 +21957,6 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.3.1.tgz",
 | 
			
		||||
      "integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw=="
 | 
			
		||||
    },
 | 
			
		||||
    "@mempool/chartist": {
 | 
			
		||||
      "version": "0.11.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@mempool/chartist/-/chartist-0.11.4.tgz",
 | 
			
		||||
      "integrity": "sha512-wSemsw2NIWS7/SHxjDe9upSdUETxNRebY0ByaJzcONKUzJSUzMuSNmKEdD3kr/g02H++JvsXR2znLC6tYEAbPA=="
 | 
			
		||||
    },
 | 
			
		||||
    "@mempool/mempool.js": {
 | 
			
		||||
      "version": "2.2.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@mempool/mempool.js/-/mempool.js-2.2.4.tgz",
 | 
			
		||||
 | 
			
		||||
@ -68,7 +68,6 @@
 | 
			
		||||
    "@fortawesome/fontawesome-svg-core": "^1.2.35",
 | 
			
		||||
    "@fortawesome/free-solid-svg-icons": "^5.15.3",
 | 
			
		||||
    "@juggle/resize-observer": "^3.3.1",
 | 
			
		||||
    "@mempool/chartist": "^0.11.4",
 | 
			
		||||
    "@mempool/mempool.js": "^2.2.4",
 | 
			
		||||
    "@ng-bootstrap/ng-bootstrap": "^7.0.0",
 | 
			
		||||
    "@nguniversal/express-engine": "11.2.1",
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,46 @@ export const mempoolFeeColors = [
 | 
			
		||||
  'b9254b',
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const chartColors = [
 | 
			
		||||
  "#D81B60",
 | 
			
		||||
  "#8E24AA",
 | 
			
		||||
  "#5E35B1",
 | 
			
		||||
  "#3949AB",
 | 
			
		||||
  "#1E88E5",
 | 
			
		||||
  "#039BE5",
 | 
			
		||||
  "#00ACC1",
 | 
			
		||||
  "#00897B",
 | 
			
		||||
  "#43A047",
 | 
			
		||||
  "#7CB342",
 | 
			
		||||
  "#C0CA33",
 | 
			
		||||
  "#FDD835",
 | 
			
		||||
  "#FFB300",
 | 
			
		||||
  "#FB8C00",
 | 
			
		||||
  "#F4511E",
 | 
			
		||||
  "#6D4C41",
 | 
			
		||||
  "#757575",
 | 
			
		||||
  "#546E7A",
 | 
			
		||||
  "#b71c1c",
 | 
			
		||||
  "#880E4F",
 | 
			
		||||
  "#4A148C",
 | 
			
		||||
  "#311B92",
 | 
			
		||||
  "#1A237E",
 | 
			
		||||
  "#0D47A1",
 | 
			
		||||
  "#01579B",
 | 
			
		||||
  "#006064",
 | 
			
		||||
  "#004D40",
 | 
			
		||||
  "#1B5E20",
 | 
			
		||||
  "#33691E",
 | 
			
		||||
  "#827717",
 | 
			
		||||
  "#F57F17",
 | 
			
		||||
  "#FF6F00",
 | 
			
		||||
  "#E65100",
 | 
			
		||||
  "#BF360C",
 | 
			
		||||
  "#3E2723",
 | 
			
		||||
  "#212121",
 | 
			
		||||
  "#263238",
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const feeLevels = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
 | 
			
		||||
  250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -27,13 +27,13 @@ import { LiquidMasterPageComponent } from './components/liquid-master-page/liqui
 | 
			
		||||
import { AboutComponent } from './components/about/about.component';
 | 
			
		||||
import { TelevisionComponent } from './components/television/television.component';
 | 
			
		||||
import { StatisticsComponent } from './components/statistics/statistics.component';
 | 
			
		||||
import { ChartistComponent } from './components/statistics/chartist.component';
 | 
			
		||||
import { BlockchainBlocksComponent } from './components/blockchain-blocks/blockchain-blocks.component';
 | 
			
		||||
import { BlockchainComponent } from './components/blockchain/blockchain.component';
 | 
			
		||||
import { FooterComponent } from './components/footer/footer.component';
 | 
			
		||||
import { AudioService } from './services/audio.service';
 | 
			
		||||
import { MempoolBlockComponent } from './components/mempool-block/mempool-block.component';
 | 
			
		||||
import { FeeDistributionGraphComponent } from './components/fee-distribution-graph/fee-distribution-graph.component';
 | 
			
		||||
import { IncomingTransactionsGraphComponent } from './components/incoming-transactions-graph/incoming-transactions-graph.component';
 | 
			
		||||
import { TimeSpanComponent } from './components/time-span/time-span.component';
 | 
			
		||||
import { SeoService } from './services/seo.service';
 | 
			
		||||
import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph.component';
 | 
			
		||||
@ -79,10 +79,10 @@ import { SponsorComponent } from './components/sponsor/sponsor.component';
 | 
			
		||||
    TimeSpanComponent,
 | 
			
		||||
    AddressLabelsComponent,
 | 
			
		||||
    MempoolBlocksComponent,
 | 
			
		||||
    ChartistComponent,
 | 
			
		||||
    FooterComponent,
 | 
			
		||||
    MempoolBlockComponent,
 | 
			
		||||
    FeeDistributionGraphComponent,
 | 
			
		||||
    IncomingTransactionsGraphComponent,
 | 
			
		||||
    MempoolGraphComponent,
 | 
			
		||||
    AssetComponent,
 | 
			
		||||
    AssetsComponent,
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,5 @@
 | 
			
		||||
<div style="height: 225px;" *ngIf="mempoolVsizeFeesData; else loadingFees">
 | 
			
		||||
  <app-chartist
 | 
			
		||||
    [data]="mempoolVsizeFeesData"
 | 
			
		||||
    [type]="'Line'"
 | 
			
		||||
    [options]="mempoolVsizeFeesOptions">
 | 
			
		||||
  </app-chartist>
 | 
			
		||||
<div class="fee-distribution-chart" *ngIf="mempoolVsizeFeesOptions; else loadingFees">
 | 
			
		||||
  <div echarts [options]="mempoolVsizeFeesOptions"></div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<ng-template #loadingFees>
 | 
			
		||||
 | 
			
		||||
@ -1,70 +1,80 @@
 | 
			
		||||
import { Component, Input, OnChanges, ChangeDetectionStrategy } from '@angular/core';
 | 
			
		||||
import * as Chartist from '@mempool/chartist';
 | 
			
		||||
import { OnChanges } from '@angular/core';
 | 
			
		||||
import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-fee-distribution-graph',
 | 
			
		||||
  templateUrl: './fee-distribution-graph.component.html',
 | 
			
		||||
  styleUrls: ['./fee-distribution-graph.component.scss'],
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
export class FeeDistributionGraphComponent implements OnChanges {
 | 
			
		||||
  @Input() feeRange;
 | 
			
		||||
export class FeeDistributionGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
  @Input() data: any;
 | 
			
		||||
  @Input() height: number | string = 210;
 | 
			
		||||
  @Input() top: number | string = 20;
 | 
			
		||||
  @Input() right: number | string = 22;
 | 
			
		||||
  @Input() left: number | string = 30;
 | 
			
		||||
 | 
			
		||||
  mempoolVsizeFeesData: any;
 | 
			
		||||
  mempoolVsizeFeesOptions: any;
 | 
			
		||||
 | 
			
		||||
  feeLevels = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
 | 
			
		||||
  250, 300, 350, 400, 500];
 | 
			
		||||
  constructor() { }
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  ngOnChanges() {
 | 
			
		||||
    this.mempoolVsizeFeesOptions = {
 | 
			
		||||
      showArea: true,
 | 
			
		||||
      showLine: true,
 | 
			
		||||
      fullWidth: true,
 | 
			
		||||
      showPoint: true,
 | 
			
		||||
      low: 0,
 | 
			
		||||
      axisY: {
 | 
			
		||||
        showLabel: false,
 | 
			
		||||
        offset: 0
 | 
			
		||||
      },
 | 
			
		||||
      axisX: {
 | 
			
		||||
        showGrid: true,
 | 
			
		||||
        showLabel: false,
 | 
			
		||||
        offset: 0
 | 
			
		||||
      },
 | 
			
		||||
      plugins: [
 | 
			
		||||
        Chartist.plugins.ctPointLabels({
 | 
			
		||||
          textAnchor: 'middle',
 | 
			
		||||
          labelInterpolationFnc: (value) => Math.round(value)
 | 
			
		||||
        })
 | 
			
		||||
      ]
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const fees = this.feeRange;
 | 
			
		||||
    const series = [];
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < this.feeLevels.length; i++) {
 | 
			
		||||
      let total = 0;
 | 
			
		||||
      // for (let j = 0; j < fees.length; j++) {
 | 
			
		||||
      for (const fee of fees) {
 | 
			
		||||
        if (i === this.feeLevels.length - 1) {
 | 
			
		||||
          if (fee >= this.feeLevels[i]) {
 | 
			
		||||
            total += 1;
 | 
			
		||||
          }
 | 
			
		||||
        } else  if (fee >= this.feeLevels[i] && fee < this.feeLevels[i + 1]) {
 | 
			
		||||
          total += 1;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      series.push(total);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.mempoolVsizeFeesData = {
 | 
			
		||||
      series: [fees],
 | 
			
		||||
      labels: fees.map((d, i) => i)
 | 
			
		||||
    };
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
    this.mountChart();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnChanges() {
 | 
			
		||||
    this.mountChart();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  mountChart() {
 | 
			
		||||
    this.mempoolVsizeFeesOptions = {
 | 
			
		||||
      grid: {
 | 
			
		||||
        height: '210',
 | 
			
		||||
        right: '20',
 | 
			
		||||
        top: '22',
 | 
			
		||||
        left: '30',
 | 
			
		||||
      },
 | 
			
		||||
      xAxis: {
 | 
			
		||||
        type: 'category',
 | 
			
		||||
        boundaryGap: false,
 | 
			
		||||
      },
 | 
			
		||||
      yAxis: {
 | 
			
		||||
        type: 'value',
 | 
			
		||||
        splitLine: {
 | 
			
		||||
          lineStyle: {
 | 
			
		||||
            type: 'dotted',
 | 
			
		||||
            color: '#ffffff66',
 | 
			
		||||
            opacity: 0.25,
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      series: [{
 | 
			
		||||
        data: this.data,
 | 
			
		||||
        type: 'line',
 | 
			
		||||
        label: {
 | 
			
		||||
          show: true,
 | 
			
		||||
          position: 'top',
 | 
			
		||||
          color: '#ffffff',
 | 
			
		||||
          textShadowBlur: 0,
 | 
			
		||||
          formatter: (label: any) => {
 | 
			
		||||
            return Math.floor(label.data);
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        smooth: true,
 | 
			
		||||
        lineStyle: {
 | 
			
		||||
          color: '#D81B60',
 | 
			
		||||
          width: 4,
 | 
			
		||||
        },
 | 
			
		||||
        itemStyle: {
 | 
			
		||||
          color: '#b71c1c',
 | 
			
		||||
          borderWidth: 10,
 | 
			
		||||
          borderMiterLimit: 10,
 | 
			
		||||
          opacity: 1,
 | 
			
		||||
        },
 | 
			
		||||
        areaStyle: {
 | 
			
		||||
          color: '#D81B60',
 | 
			
		||||
          opacity: 1,
 | 
			
		||||
        }
 | 
			
		||||
      }]
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1 @@
 | 
			
		||||
<div class="echarts" echarts [options]="mempoolStatsChartOption"></div>
 | 
			
		||||
@ -0,0 +1,162 @@
 | 
			
		||||
import { Component, OnInit, Input, Inject, LOCALE_ID, ChangeDetectionStrategy } from '@angular/core';
 | 
			
		||||
import { formatDate } from '@angular/common';
 | 
			
		||||
import { EChartsOption } from 'echarts';
 | 
			
		||||
import { OnChanges } from '@angular/core';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-incoming-transactions-graph',
 | 
			
		||||
  templateUrl: './incoming-transactions-graph.component.html',
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
  @Input() data: any;
 | 
			
		||||
  @Input() theme: string;
 | 
			
		||||
  @Input() height: number | string = '200';
 | 
			
		||||
  @Input() right: number | string = '10';
 | 
			
		||||
  @Input() top: number | string = '20';
 | 
			
		||||
  @Input() left: number | string = '50';
 | 
			
		||||
 | 
			
		||||
  mempoolStatsChartOption: EChartsOption = {};
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    @Inject(LOCALE_ID) private locale: string,
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  ngOnChanges(): void {
 | 
			
		||||
    this.mountChart();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.mountChart();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  mountChart(): void {
 | 
			
		||||
    this.mempoolStatsChartOption = {
 | 
			
		||||
      grid: {
 | 
			
		||||
        height: this.height,
 | 
			
		||||
        right: this.right,
 | 
			
		||||
        top: this.top,
 | 
			
		||||
        left: this.left,
 | 
			
		||||
      },
 | 
			
		||||
      tooltip: {
 | 
			
		||||
        trigger: 'axis',
 | 
			
		||||
        position: (pos, params, el, elRect, size) => {
 | 
			
		||||
          const obj = { top: -20 };
 | 
			
		||||
          obj[['left', 'right'][+(pos[0] < size.viewSize[0] / 2)]] = 80;
 | 
			
		||||
          return obj;
 | 
			
		||||
        },
 | 
			
		||||
        extraCssText: `background: transparent;
 | 
			
		||||
                      border: none;
 | 
			
		||||
                      box-shadow: none;`,
 | 
			
		||||
        axisPointer: {
 | 
			
		||||
          type: 'cross',
 | 
			
		||||
          label: {
 | 
			
		||||
            formatter: (axis: any) => {
 | 
			
		||||
              if (axis.axisDimension === 'y') {
 | 
			
		||||
                return `${Math.floor(axis.value)}`;
 | 
			
		||||
              }
 | 
			
		||||
              if (axis.axisDimension === 'x') {
 | 
			
		||||
                return axis.value;
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        formatter: (params: any) => {
 | 
			
		||||
          const colorSpan = (color: string) => `<div class="indicator" style="background-color: ` + color + `"></div>`;
 | 
			
		||||
          let itemFormatted = '<div>' + params[0].axisValue + '</div>';
 | 
			
		||||
          params.map((item: any, index: number) => {
 | 
			
		||||
            if (index < 26) {
 | 
			
		||||
              itemFormatted += `<div class="item">
 | 
			
		||||
                ${colorSpan(item.color)}
 | 
			
		||||
                <div class="grow"></div>
 | 
			
		||||
                <div class="value">${item.value}</div>
 | 
			
		||||
              </div>`;
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
          if (this.theme !== '') {
 | 
			
		||||
            return `<div class="tx-wrapper-tooltip-chart ${this.theme}">${itemFormatted}</div>`;
 | 
			
		||||
          }
 | 
			
		||||
          return `<div class="tx-wrapper-tooltip-chart">${itemFormatted}</div>`;
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      xAxis: {
 | 
			
		||||
        type: 'category',
 | 
			
		||||
        data: this.data.labels.map((value: any) => formatDate(value, 'HH:mm', this.locale)),
 | 
			
		||||
      },
 | 
			
		||||
      yAxis: {
 | 
			
		||||
        type: 'value',
 | 
			
		||||
        splitLine: {
 | 
			
		||||
          lineStyle: {
 | 
			
		||||
            type: 'dotted',
 | 
			
		||||
            color: '#ffffff66',
 | 
			
		||||
            opacity: 0.25,
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      series: [
 | 
			
		||||
        {
 | 
			
		||||
          data: this.data.series[0],
 | 
			
		||||
          type: 'line',
 | 
			
		||||
          smooth: true,
 | 
			
		||||
          showSymbol: false,
 | 
			
		||||
          lineStyle: {
 | 
			
		||||
            width: 3,
 | 
			
		||||
          },
 | 
			
		||||
          markLine: {
 | 
			
		||||
            silent: true,
 | 
			
		||||
            symbol: 'none',
 | 
			
		||||
            lineStyle: {
 | 
			
		||||
              color: '#fff',
 | 
			
		||||
              opacity: 0.75,
 | 
			
		||||
              width: 2,
 | 
			
		||||
            },
 | 
			
		||||
            data: [{
 | 
			
		||||
              yAxis: 1667,
 | 
			
		||||
              label: {
 | 
			
		||||
                show: false,
 | 
			
		||||
                color: '#ffffff',
 | 
			
		||||
              }
 | 
			
		||||
            }],
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      visualMap: {
 | 
			
		||||
        show: false,
 | 
			
		||||
        top: 50,
 | 
			
		||||
        right: 10,
 | 
			
		||||
        pieces: [{
 | 
			
		||||
          gt: 0,
 | 
			
		||||
          lte: 1667,
 | 
			
		||||
          color: '#7CB342'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          gt: 1667,
 | 
			
		||||
          lte: 2000,
 | 
			
		||||
          color: '#FDD835'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          gt: 2000,
 | 
			
		||||
          lte: 2500,
 | 
			
		||||
          color: '#FFB300'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          gt: 2500,
 | 
			
		||||
          lte: 3000,
 | 
			
		||||
          color: '#FB8C00'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          gt: 3000,
 | 
			
		||||
          lte: 3500,
 | 
			
		||||
          color: '#F4511E'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          gt: 3500,
 | 
			
		||||
          color: '#D81B60'
 | 
			
		||||
        }],
 | 
			
		||||
        outOfRange: {
 | 
			
		||||
          color: '#999'
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -9,7 +9,7 @@
 | 
			
		||||
 | 
			
		||||
  <div class="box">
 | 
			
		||||
    <div class="row">
 | 
			
		||||
      <div class="col-sm">
 | 
			
		||||
      <div class="col-md">
 | 
			
		||||
        <table class="table table-borderless table-striped">
 | 
			
		||||
          <tbody>
 | 
			
		||||
            <tr>
 | 
			
		||||
@ -40,8 +40,8 @@
 | 
			
		||||
          </tbody>
 | 
			
		||||
        </table>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="col-sm">
 | 
			
		||||
        <app-fee-distribution-graph [feeRange]="mempoolBlock.feeRange"></app-fee-distribution-graph>
 | 
			
		||||
      <div class="col-md chart-container">
 | 
			
		||||
        <app-fee-distribution-graph [data]="mempoolBlock.feeRange" ></app-fee-distribution-graph>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
@ -13,11 +13,8 @@
 | 
			
		||||
 | 
			
		||||
.fiat {
 | 
			
		||||
  font-size: 13px;
 | 
			
		||||
  display: block;
 | 
			
		||||
  @media (min-width: 992px) {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    margin-left: 10px;
 | 
			
		||||
  }
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  margin-left: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.table {
 | 
			
		||||
@ -38,4 +35,11 @@ h1 {
 | 
			
		||||
    float: left;
 | 
			
		||||
    margin-right: 10px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chart-container{
 | 
			
		||||
  margin: 20px auto;
 | 
			
		||||
  @media (min-width: 768px) {
 | 
			
		||||
    margin: auto;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1 @@
 | 
			
		||||
<app-chartist
 | 
			
		||||
  *ngIf="mempoolVsizeFeesData"
 | 
			
		||||
  [data]="mempoolVsizeFeesData"
 | 
			
		||||
  [type]="'Line'"
 | 
			
		||||
  [options]="mempoolVsizeFeesOptions">
 | 
			
		||||
</app-chartist>
 | 
			
		||||
<div class="echarts" echarts [options]="mempoolVsizeFeesOptions"></div>
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,18 @@
 | 
			
		||||
import { Component, OnInit, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnChanges } from '@angular/core';
 | 
			
		||||
import { formatDate } from '@angular/common';
 | 
			
		||||
import { VbytesPipe } from 'src/app/shared/pipes/bytes-pipe/vbytes.pipe';
 | 
			
		||||
import * as Chartist from '@mempool/chartist';
 | 
			
		||||
import { OptimizedMempoolStats } from 'src/app/interfaces/node-api.interface';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { StorageService } from 'src/app/services/storage.service';
 | 
			
		||||
import { EChartsOption } from 'echarts';
 | 
			
		||||
import { feeLevels, chartColors } from 'src/app/app.constants';
 | 
			
		||||
 | 
			
		||||
interface AxisObject {
 | 
			
		||||
  axisDimension: string;
 | 
			
		||||
  axisIndex: number;
 | 
			
		||||
  seriesData: any;
 | 
			
		||||
  value: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-mempool-graph',
 | 
			
		||||
@ -12,111 +20,50 @@ import { StorageService } from 'src/app/services/storage.service';
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
  @Input() data;
 | 
			
		||||
  @Input() data: any[];
 | 
			
		||||
  @Input() limitFee = 300;
 | 
			
		||||
  @Input() height: number | string = 200;
 | 
			
		||||
  @Input() top: number | string = 20;
 | 
			
		||||
  @Input() right: number | string = 10;
 | 
			
		||||
  @Input() left: number | string = 75;
 | 
			
		||||
  @Input() dateSpan = '2h';
 | 
			
		||||
  @Input() showLegend = true;
 | 
			
		||||
  @Input() offsetX = 40;
 | 
			
		||||
  @Input() small = false;
 | 
			
		||||
 | 
			
		||||
  mempoolVsizeFeesOptions: any;
 | 
			
		||||
  mempoolVsizeFeesData: any;
 | 
			
		||||
  mempoolVsizeFeesOptions: EChartsOption;
 | 
			
		||||
 | 
			
		||||
  isMobile = window.innerWidth <= 767.98;
 | 
			
		||||
  inverted: boolean;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private vbytesPipe: VbytesPipe,
 | 
			
		||||
    private stateService: StateService,
 | 
			
		||||
    @Inject(LOCALE_ID) private locale: string,
 | 
			
		||||
    private storageService: StorageService,
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    let labelHops = !this.showLegend ? 48 : 24;
 | 
			
		||||
    if (this.small) {
 | 
			
		||||
      labelHops = labelHops / 2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this.isMobile) {
 | 
			
		||||
      labelHops = 96;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const labelInterpolationFnc = (value: any, index: any) => {
 | 
			
		||||
      switch (this.dateSpan) {
 | 
			
		||||
        case '2h':
 | 
			
		||||
        case '24h':
 | 
			
		||||
          value = formatDate(value, 'HH:mm', this.locale);
 | 
			
		||||
          break;
 | 
			
		||||
        case '1w':
 | 
			
		||||
          value = formatDate(value, 'dd/MM HH:mm', this.locale);
 | 
			
		||||
          break;
 | 
			
		||||
        case '1m':
 | 
			
		||||
        case '3m':
 | 
			
		||||
        case '6m':
 | 
			
		||||
        case '1y':
 | 
			
		||||
          value = formatDate(value, 'dd/MM', this.locale);
 | 
			
		||||
      }
 | 
			
		||||
      return index % labelHops === 0 ? value : null;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    this.mempoolVsizeFeesOptions = {
 | 
			
		||||
      showArea: true,
 | 
			
		||||
      showLine: false,
 | 
			
		||||
      fullWidth: true,
 | 
			
		||||
      showPoint: false,
 | 
			
		||||
      stackedLine: !this.inverted,
 | 
			
		||||
      low: 0,
 | 
			
		||||
      axisX: {
 | 
			
		||||
        labelInterpolationFnc: labelInterpolationFnc,
 | 
			
		||||
        offset: this.offsetX,
 | 
			
		||||
      },
 | 
			
		||||
      axisY: {
 | 
			
		||||
        labelInterpolationFnc: (value: number): any => this.vbytesPipe.transform(value, 2, 'vB', 'MvB', true),
 | 
			
		||||
        offset: this.showLegend ? 160 : 60,
 | 
			
		||||
      },
 | 
			
		||||
      plugins: this.inverted ? [Chartist.plugins.ctTargetLine({ value: this.stateService.blockVSize })] : []
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (this.showLegend) {
 | 
			
		||||
      const legendNames: string[] = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
 | 
			
		||||
            250, 300, 350, 400].map((sat, i, arr) => {
 | 
			
		||||
              if (sat === 400) {
 | 
			
		||||
                return '350+';
 | 
			
		||||
              }
 | 
			
		||||
              if (i === 0) {
 | 
			
		||||
                return '0 - 1';
 | 
			
		||||
              }
 | 
			
		||||
              return arr[i - 1] + ' - ' + sat;
 | 
			
		||||
            });
 | 
			
		||||
      // Only Liquid has lower than 1 sat/vb transactions
 | 
			
		||||
      if (this.stateService.network !== 'liquid') {
 | 
			
		||||
        legendNames.shift();
 | 
			
		||||
      }
 | 
			
		||||
      this.mempoolVsizeFeesOptions.plugins.push(
 | 
			
		||||
        Chartist.plugins.legend({ legendNames: legendNames })
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    this.mountFeeChart();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnChanges() {
 | 
			
		||||
    this.inverted = this.storageService.getValue('inverted-graph') === 'true';
 | 
			
		||||
    // this.inverted = this.storageService.getValue('inverted-graph') === 'true';
 | 
			
		||||
    this.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([]));
 | 
			
		||||
    this.mountFeeChart();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleNewMempoolData(mempoolStats: OptimizedMempoolStats[]) {
 | 
			
		||||
    mempoolStats.reverse();
 | 
			
		||||
    const labels = mempoolStats.map(stats => stats.added);
 | 
			
		||||
 | 
			
		||||
    const finalArrayVbyte = this.generateArray(mempoolStats);
 | 
			
		||||
    const finalArrayVByte = this.generateArray(mempoolStats);
 | 
			
		||||
 | 
			
		||||
    // Only Liquid has lower than 1 sat/vb transactions
 | 
			
		||||
    if (this.stateService.network !== 'liquid') {
 | 
			
		||||
      finalArrayVbyte.shift();
 | 
			
		||||
      finalArrayVByte.shift();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      labels: labels,
 | 
			
		||||
      series: finalArrayVbyte
 | 
			
		||||
      series: finalArrayVByte
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -134,12 +81,128 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
          feesArray.push(0);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      if (this.inverted && finalArray.length) {
 | 
			
		||||
        feesArray = feesArray.map((value, i) => value + finalArray[finalArray.length - 1][i]);
 | 
			
		||||
      }
 | 
			
		||||
      // if (this.inverted && finalArray.length) {
 | 
			
		||||
      //   feesArray = feesArray.map((value, i) => value + finalArray[finalArray.length - 1][i]);
 | 
			
		||||
      // }
 | 
			
		||||
      finalArray.push(feesArray);
 | 
			
		||||
    }
 | 
			
		||||
    finalArray.reverse();
 | 
			
		||||
    return finalArray;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  mountFeeChart(){
 | 
			
		||||
    const { labels, series } = this.mempoolVsizeFeesData;
 | 
			
		||||
 | 
			
		||||
    const legendNames: string[] = feeLevels.map((sat, i, arr) => {
 | 
			
		||||
      if (sat > this.limitFee) { return `${this.limitFee}+`; }
 | 
			
		||||
      if (i === 0) { return '0 - 1'; }
 | 
			
		||||
      return arr[i - 1] + ' - ' + sat;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const yAxisSeries = series.map((value: Array<number>, index: number) => {
 | 
			
		||||
      return {
 | 
			
		||||
        name: labels[index].name,
 | 
			
		||||
        type: 'line',
 | 
			
		||||
        stack: 'total',
 | 
			
		||||
        smooth: false,
 | 
			
		||||
        lineStyle: {
 | 
			
		||||
          width: 0,
 | 
			
		||||
          opacity: 0,
 | 
			
		||||
        },
 | 
			
		||||
        showSymbol: false,
 | 
			
		||||
        areaStyle: {
 | 
			
		||||
          opacity: 1,
 | 
			
		||||
          color: chartColors[index],
 | 
			
		||||
        },
 | 
			
		||||
        emphasis: {
 | 
			
		||||
          focus: 'series'
 | 
			
		||||
        },
 | 
			
		||||
        markLine: {
 | 
			
		||||
          symbol: 'none',
 | 
			
		||||
          itemStyle: {
 | 
			
		||||
            borderWidth: 0,
 | 
			
		||||
            borderColor: 'none',
 | 
			
		||||
            color: '#fff',
 | 
			
		||||
          },
 | 
			
		||||
          lineStyle: {
 | 
			
		||||
            color: '#fff',
 | 
			
		||||
            opacity: 0.75,
 | 
			
		||||
            width: 2,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        data: this.vbytesPipe.transform(value, 2, 'vB', 'MvB', true)
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.mempoolVsizeFeesOptions = {
 | 
			
		||||
      color: chartColors,
 | 
			
		||||
      tooltip: {
 | 
			
		||||
        trigger: 'axis',
 | 
			
		||||
        position: (pos, params, el, elRect, size) => {
 | 
			
		||||
          const positions = { top: -20 };
 | 
			
		||||
          positions[['left', 'right'][+(pos[0] < size.viewSize[0] / 2)]] = 80;
 | 
			
		||||
          return positions;
 | 
			
		||||
        },
 | 
			
		||||
        extraCssText: `width: 150px;
 | 
			
		||||
                      background: transparent;
 | 
			
		||||
                      border: none;
 | 
			
		||||
                      box-shadow: none;`,
 | 
			
		||||
        axisPointer: {
 | 
			
		||||
          type: 'cross',
 | 
			
		||||
          label: {
 | 
			
		||||
            formatter: (axis: AxisObject) => {
 | 
			
		||||
              if (axis.axisDimension === 'y') {
 | 
			
		||||
                return `${this.vbytesPipe.transform(axis.value, 2, 'vB', 'MvB', true)}`;
 | 
			
		||||
              }
 | 
			
		||||
              if (axis.axisDimension === 'x') {
 | 
			
		||||
                return axis.value;
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        formatter: (params: any) => {
 | 
			
		||||
          const colorSpan = (index: number) => `<div class="indicator" style="background-color: ` + chartColors[index] + `"></div>`;
 | 
			
		||||
          const legendName = (index: number) => legendNames[index];
 | 
			
		||||
          let itemFormatted = '<div>' + params[0].axisValue + '</div>';
 | 
			
		||||
          params.map((item: any, index: number) => {
 | 
			
		||||
            if (feeLevels[index - 1] < this.limitFee) {
 | 
			
		||||
              itemFormatted += `<div class="item">
 | 
			
		||||
                ${colorSpan(index - 1)} ${legendName(index)}
 | 
			
		||||
                <div class="grow"></div>
 | 
			
		||||
                <div class="value">${this.vbytesPipe.transform(item.value, 2, 'vB', 'MvB', true)}</div>
 | 
			
		||||
              </div>`;
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
          return `<div class="fees-wrapper-tooltip-chart">${itemFormatted}</div>`;
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      grid: {
 | 
			
		||||
        height: this.height,
 | 
			
		||||
        right: this.right,
 | 
			
		||||
        top: this.top,
 | 
			
		||||
        left: this.left,
 | 
			
		||||
      },
 | 
			
		||||
      xAxis: [
 | 
			
		||||
        {
 | 
			
		||||
          type: 'category',
 | 
			
		||||
          boundaryGap: false,
 | 
			
		||||
          data: labels.map((value: any) => formatDate(value, 'HH:mm', this.locale)),
 | 
			
		||||
        }
 | 
			
		||||
      ],
 | 
			
		||||
      yAxis: {
 | 
			
		||||
        type: 'value',
 | 
			
		||||
        axisLabel: {
 | 
			
		||||
          formatter: (value: number) => (`${this.vbytesPipe.transform(value, 2, 'vB', 'MvB', true)}`),
 | 
			
		||||
        },
 | 
			
		||||
        splitLine: {
 | 
			
		||||
          lineStyle: {
 | 
			
		||||
            type: 'dotted',
 | 
			
		||||
            color: '#ffffff66',
 | 
			
		||||
            opacity: 0.25,
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      series: yAxisSeries
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,11 +0,0 @@
 | 
			
		||||
.ct-legend {
 | 
			
		||||
    top: 130px;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column-reverse;
 | 
			
		||||
    @media (min-width: 653px) {
 | 
			
		||||
        top: 90px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .ct-legend.inverted {
 | 
			
		||||
    flex-direction: column !important;
 | 
			
		||||
  }
 | 
			
		||||
@ -1,740 +0,0 @@
 | 
			
		||||
import {
 | 
			
		||||
  Component,
 | 
			
		||||
  ElementRef,
 | 
			
		||||
  Inject,
 | 
			
		||||
  Input,
 | 
			
		||||
  OnChanges,
 | 
			
		||||
  OnDestroy,
 | 
			
		||||
  OnInit,
 | 
			
		||||
  PLATFORM_ID,
 | 
			
		||||
  SimpleChanges,
 | 
			
		||||
  ViewEncapsulation
 | 
			
		||||
} from '@angular/core';
 | 
			
		||||
 | 
			
		||||
import { isPlatformBrowser } from '@angular/common';
 | 
			
		||||
 | 
			
		||||
import * as Chartist from '@mempool/chartist';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Possible chart types
 | 
			
		||||
 * @type {String}
 | 
			
		||||
 */
 | 
			
		||||
export type ChartType = 'Pie' | 'Bar' | 'Line';
 | 
			
		||||
 | 
			
		||||
export type ChartInterfaces =
 | 
			
		||||
  | Chartist.IChartistPieChart
 | 
			
		||||
  | Chartist.IChartistBarChart
 | 
			
		||||
  | Chartist.IChartistLineChart;
 | 
			
		||||
export type ChartOptions =
 | 
			
		||||
  | Chartist.IBarChartOptions
 | 
			
		||||
  | Chartist.ILineChartOptions
 | 
			
		||||
  | Chartist.IPieChartOptions;
 | 
			
		||||
export type ResponsiveOptionTuple = Chartist.IResponsiveOptionTuple<
 | 
			
		||||
  ChartOptions
 | 
			
		||||
>;
 | 
			
		||||
export type ResponsiveOptions = ResponsiveOptionTuple[];
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represent a chart event.
 | 
			
		||||
 * For possible values, check the Chartist docs.
 | 
			
		||||
 */
 | 
			
		||||
export interface ChartEvent {
 | 
			
		||||
  [eventName: string]: (data: any) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-chartist',
 | 
			
		||||
  template: '<ng-content></ng-content>',
 | 
			
		||||
  styleUrls: ['./chartist.component.scss'],
 | 
			
		||||
  encapsulation: ViewEncapsulation.None,
 | 
			
		||||
})
 | 
			
		||||
export class ChartistComponent implements OnInit, OnChanges, OnDestroy {
 | 
			
		||||
  @Input()
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  public data: Promise<Chartist.IChartistData> | Chartist.IChartistData;
 | 
			
		||||
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  @Input() public type: Promise<ChartType> | ChartType;
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  public options: Promise<Chartist.IChartOptions> | Chartist.IChartOptions;
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  public responsiveOptions: Promise<ResponsiveOptions> | ResponsiveOptions;
 | 
			
		||||
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  @Input() public events: ChartEvent;
 | 
			
		||||
 | 
			
		||||
  isBrowser: boolean = isPlatformBrowser(this.platformId);
 | 
			
		||||
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  public chart: ChartInterfaces;
 | 
			
		||||
 | 
			
		||||
  private element: HTMLElement;  
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    element: ElementRef,
 | 
			
		||||
    @Inject(PLATFORM_ID) private platformId: any,
 | 
			
		||||
  ) {
 | 
			
		||||
    this.element = element.nativeElement;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public ngOnInit(): Promise<ChartInterfaces> {
 | 
			
		||||
    if (!this.isBrowser) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!this.type || !this.data) {
 | 
			
		||||
      Promise.reject('Expected at least type and data.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return this.renderChart().then((chart) => {
 | 
			
		||||
      if (this.events !== undefined) {
 | 
			
		||||
        this.bindEvents(chart);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return chart;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public ngOnChanges(changes: SimpleChanges): void {
 | 
			
		||||
    if (!this.isBrowser) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.update(changes);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public ngOnDestroy(): void {
 | 
			
		||||
    if (this.chart) {
 | 
			
		||||
      this.chart.detach();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public renderChart(): Promise<ChartInterfaces> {
 | 
			
		||||
    const promises: any[] = [
 | 
			
		||||
      this.type,
 | 
			
		||||
      this.element,
 | 
			
		||||
      this.data,
 | 
			
		||||
      this.options,
 | 
			
		||||
      this.responsiveOptions
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    return Promise.all(promises).then((values) => {
 | 
			
		||||
      const [type, ...args]: any = values;
 | 
			
		||||
 | 
			
		||||
      if (!(type in Chartist)) {
 | 
			
		||||
        throw new Error(`${type} is not a valid chart type`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this.chart = (Chartist as any)[type](...args);
 | 
			
		||||
 | 
			
		||||
      return this.chart;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public update(changes: SimpleChanges): void {
 | 
			
		||||
    if (!this.chart || 'type' in changes) {
 | 
			
		||||
      this.renderChart();
 | 
			
		||||
    } else {
 | 
			
		||||
      if (changes.data) {
 | 
			
		||||
        this.data = changes.data.currentValue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (changes.options) {
 | 
			
		||||
        this.options = changes.options.currentValue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      (this.chart as any).update(this.data, this.options);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public bindEvents(chart: any): void {
 | 
			
		||||
    for (const event of Object.keys(this.events)) {
 | 
			
		||||
      chart.on(event, this.events[event]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Chartist.js plugin to display a "target" or "goal" line across the chart.
 | 
			
		||||
 * Only tested with bar charts. Works for horizontal and vertical bars.
 | 
			
		||||
 */
 | 
			
		||||
(function(window, document, Chartist) {
 | 
			
		||||
  'use strict';
 | 
			
		||||
 | 
			
		||||
  const defaultOptions = {
 | 
			
		||||
    // The class name so you can style the text
 | 
			
		||||
    className: 'ct-target-line',
 | 
			
		||||
    // The axis to draw the line. y == vertical bars, x == horizontal
 | 
			
		||||
    axis: 'y',
 | 
			
		||||
    // What value the target line should be drawn at
 | 
			
		||||
    value: null
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  Chartist.plugins = Chartist.plugins || {};
 | 
			
		||||
 | 
			
		||||
  Chartist.plugins.ctTargetLine = function (options: any) {
 | 
			
		||||
    options = Chartist.extend({}, defaultOptions, options);
 | 
			
		||||
    return function ctTargetLine (chart: any) {
 | 
			
		||||
 | 
			
		||||
      chart.on('created', function(context: any) {
 | 
			
		||||
        const projectTarget = {
 | 
			
		||||
          y: function (chartRect: any, bounds: any, value: any) {
 | 
			
		||||
            const targetLineY = chartRect.y1 - (chartRect.height() / bounds.max * value);
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
              x1: chartRect.x1,
 | 
			
		||||
              x2: chartRect.x2,
 | 
			
		||||
              y1: targetLineY,
 | 
			
		||||
              y2: targetLineY
 | 
			
		||||
            };
 | 
			
		||||
          },
 | 
			
		||||
          x: function (chartRect: any, bounds: any, value: any) {
 | 
			
		||||
            const targetLineX = chartRect.x1 + (chartRect.width() / bounds.max * value);
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
              x1: targetLineX,
 | 
			
		||||
              x2: targetLineX,
 | 
			
		||||
              y1: chartRect.y1,
 | 
			
		||||
              y2: chartRect.y2
 | 
			
		||||
            };
 | 
			
		||||
          }
 | 
			
		||||
        };
 | 
			
		||||
        // @ts-ignore
 | 
			
		||||
        const targetLine = projectTarget[options.axis](context.chartRect, context.bounds, options.value);
 | 
			
		||||
 | 
			
		||||
        context.svg.elem('line', targetLine, options.className);
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
}(null, null, Chartist));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Chartist.js plugin to display a data label on top of the points in a line chart.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
/* global Chartist */
 | 
			
		||||
(function(window, document, Chartist) {
 | 
			
		||||
  'use strict';
 | 
			
		||||
 | 
			
		||||
  const defaultOptions = {
 | 
			
		||||
    labelClass: 'ct-label',
 | 
			
		||||
    labelOffset: {
 | 
			
		||||
      x: 0,
 | 
			
		||||
      y: -10
 | 
			
		||||
    },
 | 
			
		||||
    textAnchor: 'middle',
 | 
			
		||||
    align: 'center',
 | 
			
		||||
    labelInterpolationFnc: Chartist.noop
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const labelPositionCalculation = {
 | 
			
		||||
    point: function(data: any) {
 | 
			
		||||
      return {
 | 
			
		||||
        x: data.x,
 | 
			
		||||
        y: data.y
 | 
			
		||||
      };
 | 
			
		||||
    },
 | 
			
		||||
    bar: {
 | 
			
		||||
      left: function(data: any) {
 | 
			
		||||
        return {
 | 
			
		||||
          x: data.x1,
 | 
			
		||||
          y: data.y1
 | 
			
		||||
        };
 | 
			
		||||
      },
 | 
			
		||||
      center: function(data: any) {
 | 
			
		||||
        return {
 | 
			
		||||
          x: data.x1 + (data.x2 - data.x1) / 2,
 | 
			
		||||
          y: data.y1
 | 
			
		||||
        };
 | 
			
		||||
      },
 | 
			
		||||
      right: function(data: any) {
 | 
			
		||||
        return {
 | 
			
		||||
          x: data.x2,
 | 
			
		||||
          y: data.y1
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  Chartist.plugins = Chartist.plugins || {};
 | 
			
		||||
  Chartist.plugins.ctPointLabels = function(options: any) {
 | 
			
		||||
 | 
			
		||||
    options = Chartist.extend({}, defaultOptions, options);
 | 
			
		||||
 | 
			
		||||
    function addLabel(position: any, data: any) {
 | 
			
		||||
      // if x and y exist concat them otherwise output only the existing value
 | 
			
		||||
      const value = data.value.x !== undefined && data.value.y ?
 | 
			
		||||
        (data.value.x + ', ' + data.value.y) :
 | 
			
		||||
        data.value.y || data.value.x;
 | 
			
		||||
 | 
			
		||||
      data.group.elem('text', {
 | 
			
		||||
        x: position.x + options.labelOffset.x,
 | 
			
		||||
        y: position.y + options.labelOffset.y,
 | 
			
		||||
        style: 'text-anchor: ' + options.textAnchor
 | 
			
		||||
      }, options.labelClass).text(options.labelInterpolationFnc(value));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return function ctPointLabels(chart: any) {
 | 
			
		||||
      if (chart instanceof Chartist.Line || chart instanceof Chartist.Bar) {
 | 
			
		||||
        chart.on('draw', function(data: any) {
 | 
			
		||||
          // @ts-ignore
 | 
			
		||||
          const positonCalculator = labelPositionCalculation[data.type]
 | 
			
		||||
        // @ts-ignore
 | 
			
		||||
          && labelPositionCalculation[data.type][options.align] || labelPositionCalculation[data.type];
 | 
			
		||||
          if (positonCalculator) {
 | 
			
		||||
            addLabel(positonCalculator(data), data);
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
}(null, null, Chartist));
 | 
			
		||||
 | 
			
		||||
const defaultOptions = {
 | 
			
		||||
    className: '',
 | 
			
		||||
    classNames: false,
 | 
			
		||||
    removeAll: false,
 | 
			
		||||
    legendNames: false,
 | 
			
		||||
    clickable: true,
 | 
			
		||||
    onClick: null,
 | 
			
		||||
    position: 'top'
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Chartist.plugins.legend = function (options: any) {
 | 
			
		||||
    let cachedDOMPosition;
 | 
			
		||||
    let cacheInactiveLegends: { [key:number]: boolean } = {};
 | 
			
		||||
    // Catch invalid options
 | 
			
		||||
    if (options && options.position) {
 | 
			
		||||
        if (!(options.position === 'top' || options.position === 'bottom' || options.position instanceof HTMLElement)) {
 | 
			
		||||
            throw Error('The position you entered is not a valid position');
 | 
			
		||||
        }
 | 
			
		||||
        if (options.position instanceof HTMLElement) {
 | 
			
		||||
            // Detatch DOM element from options object, because Chartist.extend
 | 
			
		||||
            // currently chokes on circular references present in HTMLElements
 | 
			
		||||
            cachedDOMPosition = options.position;
 | 
			
		||||
            delete options.position;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    options = Chartist.extend({}, defaultOptions, options);
 | 
			
		||||
 | 
			
		||||
    if (cachedDOMPosition) {
 | 
			
		||||
        // Reattatch the DOM Element position if it was removed before
 | 
			
		||||
        options.position = cachedDOMPosition;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return function legend(chart: any) {
 | 
			
		||||
 | 
			
		||||
      var isSelfUpdate = false;
 | 
			
		||||
 | 
			
		||||
      chart.on('created', function (data: any) {
 | 
			
		||||
 | 
			
		||||
        const useLabels = chart instanceof Chartist.Pie && chart.data.labels && chart.data.labels.length;
 | 
			
		||||
        const legendNames = getLegendNames(useLabels);
 | 
			
		||||
        var dirtyChartData = (chart.data.series.length < legendNames.length);
 | 
			
		||||
 | 
			
		||||
        if (isSelfUpdate || dirtyChartData)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        function removeLegendElement() {
 | 
			
		||||
          const legendElement = chart.container.querySelector('.ct-legend');
 | 
			
		||||
            if (legendElement) {
 | 
			
		||||
                legendElement.parentNode.removeChild(legendElement);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Set a unique className for each series so that when a series is removed,
 | 
			
		||||
        // the other series still have the same color.
 | 
			
		||||
        function setSeriesClassNames() {
 | 
			
		||||
            chart.data.series = chart.data.series.map(function (series: any, seriesIndex: any) {
 | 
			
		||||
                if (typeof series !== 'object') {
 | 
			
		||||
                    series = {
 | 
			
		||||
                        value: series
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
                series.className = series.className || chart.options.classNames.series + '-' + Chartist.alphaNumerate(seriesIndex);
 | 
			
		||||
                return series;
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function createLegendElement() {
 | 
			
		||||
            const legendElement = document.createElement('ul');
 | 
			
		||||
            legendElement.className = 'ct-legend';
 | 
			
		||||
            const inverted = localStorage.getItem('inverted-graph') === 'true';
 | 
			
		||||
            if (inverted){
 | 
			
		||||
                legendElement.classList.add('inverted');
 | 
			
		||||
            }
 | 
			
		||||
            if (chart instanceof Chartist.Pie) {
 | 
			
		||||
                legendElement.classList.add('ct-legend-inside');
 | 
			
		||||
            }
 | 
			
		||||
            if (typeof options.className === 'string' && options.className.length > 0) {
 | 
			
		||||
                legendElement.classList.add(options.className);
 | 
			
		||||
            }
 | 
			
		||||
            if (chart.options.width) {
 | 
			
		||||
                legendElement.style.cssText = 'width: ' + chart.options.width + 'px;margin: 0 auto;';
 | 
			
		||||
            }
 | 
			
		||||
            return legendElement;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Get the right array to use for generating the legend.
 | 
			
		||||
        function getLegendNames(useLabels: any) {
 | 
			
		||||
            return options.legendNames || (useLabels ? chart.data.labels : chart.data.series);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Initialize the array that associates series with legends.
 | 
			
		||||
        // -1 indicates that there is no legend associated with it.
 | 
			
		||||
        function initSeriesMetadata(useLabels: any) {
 | 
			
		||||
            const seriesMetadata = new Array(chart.data.series.length);
 | 
			
		||||
            for (let i = 0; i < chart.data.series.length; i++) {
 | 
			
		||||
                seriesMetadata[i] = {
 | 
			
		||||
                    data: chart.data.series[i],
 | 
			
		||||
                    label: useLabels ? chart.data.labels[i] : null,
 | 
			
		||||
                    legend: -1
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
            return seriesMetadata;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function createNameElement(i: any, legendText: any, classNamesViable: any) {
 | 
			
		||||
            const li = document.createElement('li');
 | 
			
		||||
            li.classList.add('ct-series-' + i);
 | 
			
		||||
            // Append specific class to a legend element, if viable classes are given
 | 
			
		||||
            if (classNamesViable) {
 | 
			
		||||
                li.classList.add(options.classNames[i]);
 | 
			
		||||
            }
 | 
			
		||||
            li.setAttribute('data-legend', i);
 | 
			
		||||
            li.textContent = legendText;
 | 
			
		||||
            return li;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Append the legend element to the DOM
 | 
			
		||||
        function appendLegendToDOM(legendElement: any) {
 | 
			
		||||
            if (!(options.position instanceof HTMLElement)) {
 | 
			
		||||
                switch (options.position) {
 | 
			
		||||
                    case 'top':
 | 
			
		||||
                        chart.container.insertBefore(legendElement, chart.container.childNodes[0]);
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case 'bottom':
 | 
			
		||||
                        chart.container.insertBefore(legendElement, null);
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // Appends the legend element as the last child of a given HTMLElement
 | 
			
		||||
                options.position.insertBefore(legendElement, null);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function updateChart(newSeries: any, newLabels:any, useLabels: any) {
 | 
			
		||||
            chart.data.series = newSeries;
 | 
			
		||||
            if (useLabels) {
 | 
			
		||||
                chart.data.labels = newLabels;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            isSelfUpdate = true;
 | 
			
		||||
            chart.update();
 | 
			
		||||
            isSelfUpdate = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function addClickHandler(legendElement: any, legends: any, seriesMetadata: any, useLabels: any) {
 | 
			
		||||
            legendElement.addEventListener('click', function(e: any) {
 | 
			
		||||
                const li = e.target;
 | 
			
		||||
                if (li.parentNode !== legendElement || !li.hasAttribute('data-legend'))
 | 
			
		||||
                    return;
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
 | 
			
		||||
                const legendIndex = parseInt(li.getAttribute('data-legend'));
 | 
			
		||||
                const legend = legends[legendIndex];
 | 
			
		||||
 | 
			
		||||
                const activateLegend = (_legendIndex: number): void => {
 | 
			
		||||
                    legends[_legendIndex].active = true;
 | 
			
		||||
                    legendElement.childNodes[_legendIndex].classList.remove('inactive');
 | 
			
		||||
 | 
			
		||||
                    cacheInactiveLegends[_legendIndex] = false;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                const deactivateLegend = (_legendIndex: number): void => {
 | 
			
		||||
                    legends[_legendIndex].active = false;
 | 
			
		||||
                    legendElement.childNodes[_legendIndex].classList.add('inactive');
 | 
			
		||||
                    cacheInactiveLegends[_legendIndex] = true;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                for (let i = legends.length - 1; i >= 0; i--) {
 | 
			
		||||
                    if (i >= legendIndex) {
 | 
			
		||||
                        if (!legend.active) activateLegend(i);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if (legend.active) deactivateLegend(i);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                // Make sure all values are undefined (falsy) when clicking the first legend
 | 
			
		||||
                // After clicking the first legend all indices should be falsy
 | 
			
		||||
                if (legendIndex === 0) cacheInactiveLegends = {};
 | 
			
		||||
 | 
			
		||||
                const newSeries = [];
 | 
			
		||||
                const newLabels = [];
 | 
			
		||||
 | 
			
		||||
                for (let i = 0; i < seriesMetadata.length; i++) {
 | 
			
		||||
                    if (seriesMetadata[i].legend !== -1 && legends[seriesMetadata[i].legend].active) {
 | 
			
		||||
                        newSeries.push(seriesMetadata[i].data);
 | 
			
		||||
                        newLabels.push(seriesMetadata[i].label);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                updateChart(newSeries, newLabels, useLabels);
 | 
			
		||||
 | 
			
		||||
                if (options.onClick) {
 | 
			
		||||
                    options.onClick(chart, e);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        removeLegendElement();
 | 
			
		||||
 | 
			
		||||
        const legendElement = createLegendElement();
 | 
			
		||||
        const seriesMetadata = initSeriesMetadata(useLabels);
 | 
			
		||||
        const legends: any = [];
 | 
			
		||||
 | 
			
		||||
        // Check if given class names are viable to append to legends
 | 
			
		||||
        const classNamesViable = Array.isArray(options.classNames) && options.classNames.length === legendNames.length;
 | 
			
		||||
 | 
			
		||||
        var activeSeries = [];
 | 
			
		||||
        var activeLabels = [];
 | 
			
		||||
 | 
			
		||||
        // Loop through all legends to set each name in a list item.
 | 
			
		||||
        legendNames.forEach(function (legend: any, i: any) {
 | 
			
		||||
            const legendText = legend.name || legend;
 | 
			
		||||
            const legendSeries = legend.series || [i];
 | 
			
		||||
 | 
			
		||||
            const li = createNameElement(i, legendText, classNamesViable);
 | 
			
		||||
            // If the value is undefined or false, isActive is true
 | 
			
		||||
            const isActive: boolean = !cacheInactiveLegends[i];
 | 
			
		||||
            if (isActive) {
 | 
			
		||||
              activeSeries.push(seriesMetadata[i].data);
 | 
			
		||||
              activeLabels.push(seriesMetadata[i].label);
 | 
			
		||||
            } else {
 | 
			
		||||
              li.classList.add('inactive');
 | 
			
		||||
            }
 | 
			
		||||
            legendElement.appendChild(li);
 | 
			
		||||
 | 
			
		||||
            legendSeries.forEach(function(seriesIndex: any) {
 | 
			
		||||
                seriesMetadata[seriesIndex].legend = i;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            legends.push({
 | 
			
		||||
                text: legendText,
 | 
			
		||||
                series: legendSeries,
 | 
			
		||||
                active: isActive
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        appendLegendToDOM(legendElement);
 | 
			
		||||
 | 
			
		||||
        if (options.clickable) {
 | 
			
		||||
            setSeriesClassNames();
 | 
			
		||||
            addClickHandler(legendElement, legends, seriesMetadata, useLabels);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        updateChart(activeSeries, activeLabels, useLabels);
 | 
			
		||||
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Chartist.plugins.tooltip = function (options: any) {
 | 
			
		||||
  options = Chartist.extend({}, defaultOptions, options);
 | 
			
		||||
 | 
			
		||||
  return function tooltip(chart: any) {
 | 
			
		||||
    let tooltipSelector = options.pointClass;
 | 
			
		||||
    if (chart instanceof Chartist.Bar) {
 | 
			
		||||
      tooltipSelector = 'ct-bar';
 | 
			
		||||
    } else if (chart instanceof Chartist.Pie) {
 | 
			
		||||
      // Added support for donut graph
 | 
			
		||||
      if (chart.options.donut) {
 | 
			
		||||
        tooltipSelector = 'ct-slice-donut';
 | 
			
		||||
      } else {
 | 
			
		||||
        tooltipSelector = 'ct-slice-pie';
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const $chart = chart.container;
 | 
			
		||||
    let $toolTip = $chart.querySelector('.chartist-tooltip');
 | 
			
		||||
    if (!$toolTip) {
 | 
			
		||||
      $toolTip = document.createElement('div');
 | 
			
		||||
      $toolTip.className = (!options.class) ? 'chartist-tooltip' : 'chartist-tooltip ' + options.class;
 | 
			
		||||
      if (!options.appendToBody) {
 | 
			
		||||
        $chart.appendChild($toolTip);
 | 
			
		||||
      } else {
 | 
			
		||||
        document.body.appendChild($toolTip);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    let height = $toolTip.offsetHeight;
 | 
			
		||||
    let width = $toolTip.offsetWidth;
 | 
			
		||||
 | 
			
		||||
    hide($toolTip);
 | 
			
		||||
 | 
			
		||||
    function on(event: any, selector: any, callback: any) {
 | 
			
		||||
      $chart.addEventListener(event, function (e: any) {
 | 
			
		||||
        if (!selector || hasClass(e.target, selector)) {
 | 
			
		||||
          callback(e);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    on('mouseover', tooltipSelector, function (event: any) {
 | 
			
		||||
      const $point = event.target;
 | 
			
		||||
      let tooltipText = '';
 | 
			
		||||
 | 
			
		||||
      const isPieChart = (chart instanceof Chartist.Pie) ? $point : $point.parentNode;
 | 
			
		||||
      const seriesName = (isPieChart) ? $point.parentNode.getAttribute('ct:meta') || $point.parentNode.getAttribute('ct:series-name') : '';
 | 
			
		||||
      let meta = $point.getAttribute('ct:meta') || seriesName || '';
 | 
			
		||||
      const hasMeta = !!meta;
 | 
			
		||||
      let value = $point.getAttribute('ct:value');
 | 
			
		||||
 | 
			
		||||
      if (options.transformTooltipTextFnc && typeof options.transformTooltipTextFnc === 'function') {
 | 
			
		||||
        value = options.transformTooltipTextFnc(value, $point.parentNode.getAttribute('class'));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (options.tooltipFnc && typeof options.tooltipFnc === 'function') {
 | 
			
		||||
        tooltipText = options.tooltipFnc(meta, value);
 | 
			
		||||
      } else {
 | 
			
		||||
        if (options.metaIsHTML) {
 | 
			
		||||
          const txt = document.createElement('textarea');
 | 
			
		||||
          txt.innerHTML = meta;
 | 
			
		||||
          meta = txt.value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        meta = '<span class="chartist-tooltip-meta">' + meta + '</span>';
 | 
			
		||||
 | 
			
		||||
        if (hasMeta) {
 | 
			
		||||
          tooltipText += meta + '<br>';
 | 
			
		||||
        } else {
 | 
			
		||||
          // For Pie Charts also take the labels into account
 | 
			
		||||
          // Could add support for more charts here as well!
 | 
			
		||||
          if (chart instanceof Chartist.Pie) {
 | 
			
		||||
            const label = next($point, 'ct-label');
 | 
			
		||||
            if (label) {
 | 
			
		||||
              tooltipText += text(label) + '<br>';
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (value) {
 | 
			
		||||
          if (options.currency) {
 | 
			
		||||
            if (options.currencyFormatCallback != undefined) {
 | 
			
		||||
              value = options.currencyFormatCallback(value, options);
 | 
			
		||||
            } else {
 | 
			
		||||
              value = options.currency + value.replace(/(\d)(?=(\d{3})+(?:\.\d+)?$)/g, '$1,');
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          value = '<span class="chartist-tooltip-value">' + value + '</span>';
 | 
			
		||||
          tooltipText += value;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (tooltipText) {
 | 
			
		||||
        $toolTip.innerHTML = tooltipText;
 | 
			
		||||
        setPosition(event);
 | 
			
		||||
        show($toolTip);
 | 
			
		||||
 | 
			
		||||
        // Remember height and width to avoid wrong position in IE
 | 
			
		||||
        height = $toolTip.offsetHeight;
 | 
			
		||||
        width = $toolTip.offsetWidth;
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    on('mouseout', tooltipSelector, function () {
 | 
			
		||||
      hide($toolTip);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    on('mousemove', null, function (event: any) {
 | 
			
		||||
      if (false === options.anchorToPoint) {
 | 
			
		||||
        setPosition(event);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    function setPosition(event: any) {
 | 
			
		||||
      height = height || $toolTip.offsetHeight;
 | 
			
		||||
      width = width || $toolTip.offsetWidth;
 | 
			
		||||
      const offsetX = - width / 2 + options.tooltipOffset.x
 | 
			
		||||
      const offsetY = - height + options.tooltipOffset.y;
 | 
			
		||||
      let anchorX, anchorY;
 | 
			
		||||
 | 
			
		||||
      if (!options.appendToBody) {
 | 
			
		||||
        const box = $chart.getBoundingClientRect();
 | 
			
		||||
        const left = event.pageX - box.left - window.pageXOffset ;
 | 
			
		||||
        const top = event.pageY - box.top - window.pageYOffset ;
 | 
			
		||||
 | 
			
		||||
        if (true === options.anchorToPoint && event.target.x2 && event.target.y2) {
 | 
			
		||||
          anchorX = parseInt(event.target.x2.baseVal.value);
 | 
			
		||||
          anchorY = parseInt(event.target.y2.baseVal.value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $toolTip.style.top = (anchorY || top) + offsetY + 'px';
 | 
			
		||||
        $toolTip.style.left = (anchorX || left) + offsetX + 'px';
 | 
			
		||||
      } else {
 | 
			
		||||
        $toolTip.style.top = event.pageY + offsetY + 'px';
 | 
			
		||||
        $toolTip.style.left = event.pageX + offsetX + 'px';
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Chartist.plugins.ctPointLabels = (options) => {
 | 
			
		||||
  return function ctPointLabels(chart) {
 | 
			
		||||
      const defaultOptions2 = {
 | 
			
		||||
          labelClass: 'ct-point-label',
 | 
			
		||||
          labelOffset: {
 | 
			
		||||
              x: 0,
 | 
			
		||||
              y: -7
 | 
			
		||||
          },
 | 
			
		||||
          textAnchor: 'middle'
 | 
			
		||||
      };
 | 
			
		||||
      options = Chartist.extend({}, defaultOptions2, options);
 | 
			
		||||
 | 
			
		||||
      if (chart instanceof Chartist.Line) {
 | 
			
		||||
          chart.on('draw', (data) => {
 | 
			
		||||
              if (data.type === 'point') {
 | 
			
		||||
                  data.group.elem('text', {
 | 
			
		||||
                      x: data.x + options.labelOffset.x,
 | 
			
		||||
                      y: data.y + options.labelOffset.y,
 | 
			
		||||
                      style: 'text-anchor: ' + options.textAnchor
 | 
			
		||||
                  }, options.labelClass).text(options.labelInterpolationFnc(data.value.y));  // 07.11.17 added ".y"
 | 
			
		||||
              }
 | 
			
		||||
          });
 | 
			
		||||
      }
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function show(element: any) {
 | 
			
		||||
  if (!hasClass(element, 'tooltip-show')) {
 | 
			
		||||
    element.className = element.className + ' tooltip-show';
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function hide(element: any) {
 | 
			
		||||
  const regex = new RegExp('tooltip-show' + '\\s*', 'gi');
 | 
			
		||||
  element.className = element.className.replace(regex, '').trim();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function hasClass(element: any, className: any) {
 | 
			
		||||
  return (' ' + element.getAttribute('class') + ' ').indexOf(' ' + className + ' ') > -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function next(element: any, className: any) {
 | 
			
		||||
  do {
 | 
			
		||||
    element = element.nextSibling;
 | 
			
		||||
  } while (element && !hasClass(element, className));
 | 
			
		||||
  return element;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function text(element: any) {
 | 
			
		||||
  return element.innerText || element.textContent;
 | 
			
		||||
}
 | 
			
		||||
@ -36,12 +36,12 @@
 | 
			
		||||
                <input ngbButton type="radio" [value]="'1y'" [routerLink]="['/graphs' | relativeUrl]" fragment="1y"> 1Y
 | 
			
		||||
              </label>
 | 
			
		||||
            </div>
 | 
			
		||||
            <button (click)="invertGraph()" class="btn btn-primary btn-sm ml-2 d-none d-md-inline"><fa-icon [icon]="['fas', 'exchange-alt']" [rotate]="90" [fixedWidth]="true" i18n-title="statistics.component-invert.title" title="Invert"></fa-icon></button>
 | 
			
		||||
            <!-- <button (click)="invertGraph()" class="btn btn-primary btn-sm ml-2 d-none d-md-inline"><fa-icon [icon]="['fas', 'exchange-alt']" [rotate]="90" [fixedWidth]="true" i18n-title="statistics.component-invert.title" title="Invert"></fa-icon></button> -->
 | 
			
		||||
          </form>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
          <div style="height: 600px;">
 | 
			
		||||
            <app-mempool-graph dir="ltr" [data]="mempoolStats" [dateSpan]="radioGroupForm.controls.dateSpan.value"></app-mempool-graph>
 | 
			
		||||
          <div class="incoming-transactions-graph">
 | 
			
		||||
            <app-mempool-graph dir="ltr" [limitFee]="1200" [height]="550" [left]="60" [data]="mempoolStats"></app-mempool-graph>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
@ -53,12 +53,8 @@
 | 
			
		||||
            <i class="fa fa-area-chart"></i> <span i18n="statistics.transaction-vbytes-per-second">Transaction vBytes per second (vB/s)</span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
          <div style="height: 600px;">
 | 
			
		||||
            <app-chartist
 | 
			
		||||
              [data]="mempoolTransactionsWeightPerSecondData"
 | 
			
		||||
              [type]="'Line'"
 | 
			
		||||
              [options]="transactionsWeightPerSecondOptions">
 | 
			
		||||
            </app-chartist>
 | 
			
		||||
          <div class="incoming-transactions-graph">
 | 
			
		||||
            <app-incoming-transactions-graph [height]="500" [data]="mempoolTransactionsWeightPerSecondData"></app-incoming-transactions-graph>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@ -56,4 +56,8 @@
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  height: 80vh;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.incoming-transactions-graph {
 | 
			
		||||
  height: 600px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,6 @@ import { OptimizedMempoolStats } from '../../interfaces/node-api.interface';
 | 
			
		||||
import { WebsocketService } from '../../services/websocket.service';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
 | 
			
		||||
import * as Chartist from '@mempool/chartist';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { StorageService } from 'src/app/services/storage.service';
 | 
			
		||||
@ -31,8 +30,6 @@ export class StatisticsComponent implements OnInit {
 | 
			
		||||
  mempoolUnconfirmedTransactionsData: any;
 | 
			
		||||
  mempoolTransactionsWeightPerSecondData: any;
 | 
			
		||||
 | 
			
		||||
  transactionsWeightPerSecondOptions: any;
 | 
			
		||||
 | 
			
		||||
  radioGroupForm: FormGroup;
 | 
			
		||||
  inverted: boolean;
 | 
			
		||||
  graphWindowPreference: String;
 | 
			
		||||
@ -64,43 +61,6 @@ export class StatisticsComponent implements OnInit {
 | 
			
		||||
      dateSpan: this.graphWindowPreference
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const labelInterpolationFnc = (value: any, index: any) => {
 | 
			
		||||
      switch (this.graphWindowPreference) {
 | 
			
		||||
        case '2h':
 | 
			
		||||
        case '24h':
 | 
			
		||||
          value = formatDate(value, 'HH:mm', this.locale);
 | 
			
		||||
          break;
 | 
			
		||||
        case '1w':
 | 
			
		||||
          value = formatDate(value, 'dd/MM HH:mm', this.locale);
 | 
			
		||||
          break;
 | 
			
		||||
        case '1m':
 | 
			
		||||
        case '3m':
 | 
			
		||||
        case '6m':
 | 
			
		||||
        case '1y':
 | 
			
		||||
          value = formatDate(value, 'dd/MM', this.locale);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return index % labelHops === 0 ? value : null;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    this.transactionsWeightPerSecondOptions = {
 | 
			
		||||
      showArea: false,
 | 
			
		||||
      showLine: true,
 | 
			
		||||
      showPoint: false,
 | 
			
		||||
      low: 0,
 | 
			
		||||
      axisY: {
 | 
			
		||||
        offset: 40
 | 
			
		||||
      },
 | 
			
		||||
      axisX: {
 | 
			
		||||
        labelInterpolationFnc: labelInterpolationFnc
 | 
			
		||||
      },
 | 
			
		||||
      plugins: [
 | 
			
		||||
        Chartist.plugins.ctTargetLine({
 | 
			
		||||
          value: 1667
 | 
			
		||||
        }),
 | 
			
		||||
      ]
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    this.route
 | 
			
		||||
      .fragment
 | 
			
		||||
      .subscribe((fragment) => {
 | 
			
		||||
 | 
			
		||||
@ -4,12 +4,10 @@
 | 
			
		||||
    <div class="spinner-border text-light"></div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <div class="tv-container">
 | 
			
		||||
 | 
			
		||||
    <div class="chart-holder" *ngIf="mempoolStats.length">
 | 
			
		||||
      <app-mempool-graph dir="ltr" [data]="mempoolStats"></app-mempool-graph>
 | 
			
		||||
  <div class="tv-container" *ngIf="mempoolStats.length">
 | 
			
		||||
    <div class="chart-holder" >
 | 
			
		||||
      <app-mempool-graph dir="ltr" [data]="mempoolStats" [limitFee]="1200" [height]="600"></app-mempool-graph>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="blockchain-wrapper">
 | 
			
		||||
      <div class="position-container">
 | 
			
		||||
        <app-mempool-blocks></app-mempool-blocks>
 | 
			
		||||
 | 
			
		||||
@ -16,30 +16,21 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chart-holder {
 | 
			
		||||
  height: calc(100vh - 270px);
 | 
			
		||||
  min-height: 525px;
 | 
			
		||||
  padding-left: 20px;
 | 
			
		||||
  width: 98.5%;
 | 
			
		||||
  padding-top: 20px;
 | 
			
		||||
  @media(min-width: 992px){
 | 
			
		||||
    padding-top: 10px;
 | 
			
		||||
  }
 | 
			
		||||
  @media(min-height: 800px){
 | 
			
		||||
    padding-top: 60px !important;
 | 
			
		||||
  }
 | 
			
		||||
  height: 650px;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  margin: 30px auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.blockchain-wrapper {
 | 
			
		||||
 | 
			
		||||
  display: flex;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  min-height: 240px;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  top: -20px;
 | 
			
		||||
  @media(min-height: 800px) {
 | 
			
		||||
    top: 10px;
 | 
			
		||||
    top: 30px;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  .position-container {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    left: 50%;
 | 
			
		||||
@ -89,4 +80,4 @@
 | 
			
		||||
  display: flex;
 | 
			
		||||
  margin-top: 0px;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -47,8 +47,8 @@
 | 
			
		||||
              <ng-container *ngTemplateOutlet="mempoolTable; context: { $implicit: mempoolInfoData }"></ng-container>
 | 
			
		||||
              <hr>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="mempool-graph" *ngIf="(mempoolStats$ | async) as mempoolStats">
 | 
			
		||||
              <app-mempool-graph [data]="mempoolStats.mempool" [showLegend]="false" [offsetX]="20" [small]="true"></app-mempool-graph>
 | 
			
		||||
            <div class="mempool-graph" *ngIf="(mempoolStats$ | async) as mempoolStats; else loadingSpinner">
 | 
			
		||||
              <app-mempool-graph [data]="mempoolStats.mempool"></app-mempool-graph>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
@ -59,12 +59,8 @@
 | 
			
		||||
            <ng-container *ngTemplateOutlet="txPerSecond; context: { $implicit: mempoolInfoData }"></ng-container>
 | 
			
		||||
            <br>
 | 
			
		||||
            <hr>
 | 
			
		||||
            <div class="mempool-graph" *ngIf="(mempoolStats$ | async) as mempoolStats">
 | 
			
		||||
              <app-chartist
 | 
			
		||||
                  [data]="mempoolStats.weightPerSecond"
 | 
			
		||||
                  [type]="'Line'"
 | 
			
		||||
                  [options]="transactionsWeightPerSecondOptions">
 | 
			
		||||
                </app-chartist>
 | 
			
		||||
            <div class="mempool-graph" *ngIf="(mempoolStats$ | async) as mempoolStats; else loadingSpinner">
 | 
			
		||||
              <app-incoming-transactions-graph [data]="mempoolStats.weightPerSecond"></app-incoming-transactions-graph>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
@ -197,13 +193,14 @@
 | 
			
		||||
    </span>
 | 
			
		||||
    <ng-template #inSync>
 | 
			
		||||
      <div class="progress inc-tx-progress-bar">
 | 
			
		||||
        <div class="progress-bar {{ mempoolInfoData.value.progressClass }}" role="progressbar" [ngStyle]="{'width': mempoolInfoData.value.progressWidth}"> </div>
 | 
			
		||||
        <div class="progress-bar" role="progressbar" [ngStyle]="{'width': mempoolInfoData.value.progressWidth, 'background-color': mempoolInfoData.value.progressColor}"> </div>
 | 
			
		||||
        <div class="progress-text">{{ mempoolInfoData.value.vBytesPerSecond | ceil | number }} <ng-container i18n="shared.vbytes-per-second|vB/s">vB/s</ng-container></div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </ng-template>
 | 
			
		||||
  </ng-template>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<ng-template #difficultyEpoch>
 | 
			
		||||
  <div class="main-title" i18n="dashboard.difficulty-adjustment">Difficulty Adjustment</div>
 | 
			
		||||
  <div class="card-wrapper">
 | 
			
		||||
@ -228,11 +225,11 @@
 | 
			
		||||
              <ng-template #arrowDownDifficulty >
 | 
			
		||||
                <fa-icon class="retarget-sign" [icon]="['fas', 'caret-down']" [fixedWidth]="true"></fa-icon>
 | 
			
		||||
              </ng-template>
 | 
			
		||||
              {{ epochData.change | absolute | number: '1.2-2' }} 
 | 
			
		||||
              {{ epochData.change | absolute | number: '1.2-2' }}
 | 
			
		||||
              <span class="symbol">%</span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="symbol">
 | 
			
		||||
              <span i18n="difficulty-box.previous">Previous</span>: 
 | 
			
		||||
              <span i18n="difficulty-box.previous">Previous</span>:
 | 
			
		||||
              <span [ngStyle]="{'color': epochData.colorPreviousAdjustments}">
 | 
			
		||||
                <span *ngIf="epochData.previousRetarget > 0; else arrowDownPreviousDifficulty" >
 | 
			
		||||
                  <fa-icon class="previous-retarget-sign" [icon]="['fas', 'caret-up']" [fixedWidth]="true"></fa-icon>
 | 
			
		||||
@ -257,6 +254,12 @@
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<ng-template #loadingSpinner>
 | 
			
		||||
  <div class="text-center loadingGraphs">
 | 
			
		||||
    <div class="spinner-border text-light"></div>
 | 
			
		||||
  </div>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
<ng-template #loadingDifficulty>
 | 
			
		||||
  <div class="difficulty-skeleton loading-container">
 | 
			
		||||
    <div class="item">
 | 
			
		||||
 | 
			
		||||
@ -58,11 +58,11 @@
 | 
			
		||||
  display: block;
 | 
			
		||||
  @media (min-width: 485px) {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: row; 
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
  }
 | 
			
		||||
  h5 {
 | 
			
		||||
    margin-bottom: 10px;
 | 
			
		||||
  } 
 | 
			
		||||
  }
 | 
			
		||||
  .item {
 | 
			
		||||
    width: 50%;
 | 
			
		||||
    margin: 0px auto 20px;
 | 
			
		||||
@ -131,7 +131,7 @@
 | 
			
		||||
.latest-transactions {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  text-align: left;
 | 
			
		||||
  table-layout:fixed; 
 | 
			
		||||
  table-layout:fixed;
 | 
			
		||||
  tr, td, th {
 | 
			
		||||
    border: 0px;
 | 
			
		||||
  }
 | 
			
		||||
@ -220,6 +220,11 @@
 | 
			
		||||
.mempool-graph {
 | 
			
		||||
  height: 250px;
 | 
			
		||||
}
 | 
			
		||||
.loadingGraphs{
 | 
			
		||||
  height: 250px;
 | 
			
		||||
  display: grid;
 | 
			
		||||
  place-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.inc-tx-progress-bar {
 | 
			
		||||
  max-width: 250px;
 | 
			
		||||
@ -247,7 +252,7 @@
 | 
			
		||||
    color: #ffffff66;
 | 
			
		||||
    font-size: 12px;
 | 
			
		||||
  }
 | 
			
		||||
  .item {    
 | 
			
		||||
  .item {
 | 
			
		||||
    padding: 0 5px;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    &:nth-child(1) {
 | 
			
		||||
@ -276,25 +281,25 @@
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  @media (min-width: 376px) {
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
  }  
 | 
			
		||||
  }
 | 
			
		||||
  .item {
 | 
			
		||||
    max-width: 150px;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    width: -webkit-fill-available;
 | 
			
		||||
    @media (min-width: 376px) {
 | 
			
		||||
      margin: 0 auto 0px;
 | 
			
		||||
    }    
 | 
			
		||||
    }
 | 
			
		||||
    &:first-child{
 | 
			
		||||
      display: none;
 | 
			
		||||
      @media (min-width: 485px) {
 | 
			
		||||
        display: block;
 | 
			
		||||
      }    
 | 
			
		||||
      }
 | 
			
		||||
      @media (min-width: 768px) {
 | 
			
		||||
        display: none;
 | 
			
		||||
      }    
 | 
			
		||||
      }
 | 
			
		||||
      @media (min-width: 992px) {
 | 
			
		||||
        display: block;
 | 
			
		||||
      }    
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    &:last-child {
 | 
			
		||||
      margin-bottom: 0;
 | 
			
		||||
@ -355,4 +360,4 @@
 | 
			
		||||
.previous-retarget-sign {
 | 
			
		||||
  margin-right: -2px;
 | 
			
		||||
  font-size: 10px;
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -6,11 +6,11 @@ import { OptimizedMempoolStats } from '../interfaces/node-api.interface';
 | 
			
		||||
import { MempoolInfo, TransactionStripped } from '../interfaces/websocket.interface';
 | 
			
		||||
import { ApiService } from '../services/api.service';
 | 
			
		||||
import { StateService } from '../services/state.service';
 | 
			
		||||
import * as Chartist from '@mempool/chartist';
 | 
			
		||||
import { formatDate } from '@angular/common';
 | 
			
		||||
import { WebsocketService } from '../services/websocket.service';
 | 
			
		||||
import { SeoService } from '../services/seo.service';
 | 
			
		||||
import { StorageService } from '../services/storage.service';
 | 
			
		||||
import { EChartsOption } from 'echarts';
 | 
			
		||||
 | 
			
		||||
interface MempoolBlocksData {
 | 
			
		||||
  blocks: number;
 | 
			
		||||
@ -34,7 +34,7 @@ interface MempoolInfoData {
 | 
			
		||||
  memPoolInfo: MempoolInfo;
 | 
			
		||||
  vBytesPerSecond: number;
 | 
			
		||||
  progressWidth: string;
 | 
			
		||||
  progressClass: string;
 | 
			
		||||
  progressColor: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface MempoolStatsData {
 | 
			
		||||
@ -74,15 +74,15 @@ export class DashboardComponent implements OnInit {
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
 | 
			
		||||
    this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$;
 | 
			
		||||
    this.seoService.resetTitle();
 | 
			
		||||
    this.websocketService.want(['blocks', 'stats', 'mempool-blocks', 'live-2h-chart']);
 | 
			
		||||
    this.network$ = merge(of(''), this.stateService.networkChanged$);
 | 
			
		||||
    this.collapseLevel = this.storageService.getValue('dashboard-collapsed') || 'one';
 | 
			
		||||
    this.mempoolLoadingStatus$ = this.stateService.loadingIndicators$.pipe(
 | 
			
		||||
      map((indicators) => indicators.mempool !== undefined ? indicators.mempool : 100)
 | 
			
		||||
    );
 | 
			
		||||
    this.mempoolLoadingStatus$ = this.stateService.loadingIndicators$
 | 
			
		||||
      .pipe(
 | 
			
		||||
        map((indicators) => indicators.mempool !== undefined ? indicators.mempool : 100)
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
    this.mempoolInfoData$ = combineLatest([
 | 
			
		||||
      this.stateService.mempoolInfo$,
 | 
			
		||||
@ -92,11 +92,21 @@ export class DashboardComponent implements OnInit {
 | 
			
		||||
      map(([mempoolInfo, vbytesPerSecond]) => {
 | 
			
		||||
        const percent = Math.round((Math.min(vbytesPerSecond, this.vBytesPerSecondLimit) / this.vBytesPerSecondLimit) * 100);
 | 
			
		||||
 | 
			
		||||
        let progressClass = 'bg-danger';
 | 
			
		||||
        if (percent <= 75) {
 | 
			
		||||
          progressClass = 'bg-success';
 | 
			
		||||
        } else if (percent <= 99) {
 | 
			
		||||
          progressClass = 'bg-warning';
 | 
			
		||||
        let progressColor = '#7CB342';
 | 
			
		||||
        if (vbytesPerSecond > 1667) {
 | 
			
		||||
          progressColor = '#FDD835';
 | 
			
		||||
        }
 | 
			
		||||
        if (vbytesPerSecond > 2000) {
 | 
			
		||||
          progressColor = '#FFB300';
 | 
			
		||||
        }
 | 
			
		||||
        if (vbytesPerSecond > 2500) {
 | 
			
		||||
          progressColor = '#FB8C00';
 | 
			
		||||
        }
 | 
			
		||||
        if (vbytesPerSecond > 3000) {
 | 
			
		||||
          progressColor = '#F4511E';
 | 
			
		||||
        }
 | 
			
		||||
        if (vbytesPerSecond > 3500) {
 | 
			
		||||
          progressColor = '#D81B60';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const mempoolSizePercentage = (mempoolInfo.usage / mempoolInfo.maxmempool * 100);
 | 
			
		||||
@ -111,7 +121,7 @@ export class DashboardComponent implements OnInit {
 | 
			
		||||
          memPoolInfo: mempoolInfo,
 | 
			
		||||
          vBytesPerSecond: vbytesPerSecond,
 | 
			
		||||
          progressWidth: percent + '%',
 | 
			
		||||
          progressClass: progressClass,
 | 
			
		||||
          progressColor: progressColor,
 | 
			
		||||
          mempoolSizeProgress: mempoolSizeProgress,
 | 
			
		||||
        };
 | 
			
		||||
      })
 | 
			
		||||
@ -164,7 +174,7 @@ export class DashboardComponent implements OnInit {
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          let colorPreviousAdjustments = '#dc3545';
 | 
			
		||||
          if (previousRetarget){
 | 
			
		||||
          if (previousRetarget) {
 | 
			
		||||
            if (previousRetarget >= 0) {
 | 
			
		||||
              colorPreviousAdjustments = '#3bcc49';
 | 
			
		||||
            }
 | 
			
		||||
@ -191,7 +201,6 @@ export class DashboardComponent implements OnInit {
 | 
			
		||||
        })
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    this.mempoolBlocksData$ = this.stateService.mempoolBlocks$
 | 
			
		||||
      .pipe(
 | 
			
		||||
        map((mempoolBlocks) => {
 | 
			
		||||
@ -226,50 +235,32 @@ export class DashboardComponent implements OnInit {
 | 
			
		||||
        }, []),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
    this.mempoolStats$ = this.stateService.connectionState$.pipe(
 | 
			
		||||
      filter((state) => state === 2),
 | 
			
		||||
      switchMap(() => this.apiService.list2HStatistics$()),
 | 
			
		||||
      switchMap((mempoolStats) => {
 | 
			
		||||
        return merge(
 | 
			
		||||
          this.stateService.live2Chart$
 | 
			
		||||
            .pipe(
 | 
			
		||||
              scan((acc, stats) => {
 | 
			
		||||
                acc.unshift(stats);
 | 
			
		||||
                acc = acc.slice(0, 120);
 | 
			
		||||
                return acc;
 | 
			
		||||
              }, mempoolStats)
 | 
			
		||||
            ),
 | 
			
		||||
          of(mempoolStats)
 | 
			
		||||
        );
 | 
			
		||||
      }),
 | 
			
		||||
      map((mempoolStats) => {
 | 
			
		||||
        return {
 | 
			
		||||
          mempool: mempoolStats,
 | 
			
		||||
          weightPerSecond: this.handleNewMempoolData(mempoolStats.concat([])),
 | 
			
		||||
        };
 | 
			
		||||
      }),
 | 
			
		||||
      share(),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    this.transactionsWeightPerSecondOptions = {
 | 
			
		||||
        showArea: false,
 | 
			
		||||
        showLine: true,
 | 
			
		||||
        fullWidth: true,
 | 
			
		||||
        showPoint: false,
 | 
			
		||||
        low: 0,
 | 
			
		||||
        axisY: {
 | 
			
		||||
          offset: 40
 | 
			
		||||
        },
 | 
			
		||||
        axisX: {
 | 
			
		||||
          labelInterpolationFnc: (value: any, index: any) => index % 24 === 0 ? formatDate(value, 'HH:mm', this.locale) : null,
 | 
			
		||||
          offset: 20
 | 
			
		||||
        },
 | 
			
		||||
        plugins: [
 | 
			
		||||
          Chartist.plugins.ctTargetLine({
 | 
			
		||||
            value: 1667
 | 
			
		||||
          }),
 | 
			
		||||
        ]
 | 
			
		||||
      };
 | 
			
		||||
    this.mempoolStats$ = this.stateService.connectionState$
 | 
			
		||||
      .pipe(
 | 
			
		||||
        filter((state) => state === 2),
 | 
			
		||||
        switchMap(() => this.apiService.list2HStatistics$()),
 | 
			
		||||
        switchMap((mempoolStats) => {
 | 
			
		||||
          return merge(
 | 
			
		||||
            this.stateService.live2Chart$
 | 
			
		||||
              .pipe(
 | 
			
		||||
                scan((acc, stats) => {
 | 
			
		||||
                  acc.unshift(stats);
 | 
			
		||||
                  acc = acc.slice(0, 120);
 | 
			
		||||
                  return acc;
 | 
			
		||||
                }, mempoolStats)
 | 
			
		||||
              ),
 | 
			
		||||
            of(mempoolStats)
 | 
			
		||||
          );
 | 
			
		||||
        }),
 | 
			
		||||
        map((mempoolStats) => {
 | 
			
		||||
          const data = this.handleNewMempoolData(mempoolStats.concat([]));
 | 
			
		||||
          return {
 | 
			
		||||
            mempool: mempoolStats,
 | 
			
		||||
            weightPerSecond: this.handleNewMempoolData(mempoolStats.concat([])),
 | 
			
		||||
          };
 | 
			
		||||
        }),
 | 
			
		||||
        share(),
 | 
			
		||||
      );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleNewMempoolData(mempoolStats: OptimizedMempoolStats[]) {
 | 
			
		||||
 | 
			
		||||
@ -235,7 +235,7 @@ body {
 | 
			
		||||
  color: #dc3545;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.yellow-color { 
 | 
			
		||||
.yellow-color {
 | 
			
		||||
  color: #ffd800;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -255,168 +255,64 @@ html:lang(ru) .card-title {
 | 
			
		||||
  font-size: 0.9rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Chartist */
 | 
			
		||||
$ct-series-names: (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z);
 | 
			
		||||
$ct-series-colors: (
 | 
			
		||||
  #D81B60,
 | 
			
		||||
  #8E24AA,
 | 
			
		||||
  #5E35B1,
 | 
			
		||||
  #3949AB,
 | 
			
		||||
  #1E88E5,
 | 
			
		||||
  #039BE5,
 | 
			
		||||
  #00ACC1,
 | 
			
		||||
  #00897B,
 | 
			
		||||
  #43A047,
 | 
			
		||||
  #7CB342,
 | 
			
		||||
  #C0CA33,
 | 
			
		||||
  #FDD835,
 | 
			
		||||
  #FFB300,
 | 
			
		||||
  #FB8C00,
 | 
			
		||||
  #F4511E,
 | 
			
		||||
  #6D4C41,
 | 
			
		||||
  #757575,
 | 
			
		||||
  #546E7A,
 | 
			
		||||
  #b71c1c,
 | 
			
		||||
  #880E4F,
 | 
			
		||||
  #4A148C,
 | 
			
		||||
  #311B92,
 | 
			
		||||
  #1A237E,
 | 
			
		||||
  #0D47A1,
 | 
			
		||||
  #01579B,
 | 
			
		||||
  #006064,
 | 
			
		||||
  #004D40,
 | 
			
		||||
  #1B5E20,
 | 
			
		||||
  #33691E,
 | 
			
		||||
  #827717,
 | 
			
		||||
  #F57F17,
 | 
			
		||||
  #FF6F00,
 | 
			
		||||
  #E65100,
 | 
			
		||||
  #BF360C,
 | 
			
		||||
  #3E2723,
 | 
			
		||||
  #212121,
 | 
			
		||||
  #263238,
 | 
			
		||||
  #a748ca,
 | 
			
		||||
  #6188e2,
 | 
			
		||||
  #a748ca,
 | 
			
		||||
  #6188e2,
 | 
			
		||||
);
 | 
			
		||||
/* MEMPOOL CHARTS */
 | 
			
		||||
 | 
			
		||||
@import "../node_modules/@mempool/chartist/dist/scss/chartist.scss";
 | 
			
		||||
 | 
			
		||||
.ct-bar-label {
 | 
			
		||||
  font-size: 20px;
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
  fill: #fff;
 | 
			
		||||
.mempool-wrapper-tooltip-chart {
 | 
			
		||||
  height: 250px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ct-target-line {
 | 
			
		||||
  stroke: #f5f5f5;
 | 
			
		||||
  stroke-width: 3px;
 | 
			
		||||
  stroke-dasharray: 7px;
 | 
			
		||||
.echarts {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  min-height: 180px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ct-area {
 | 
			
		||||
  stroke: none;
 | 
			
		||||
  fill-opacity: 0.9;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ct-label {
 | 
			
		||||
    fill: rgba(255, 255, 255, 0.4);
 | 
			
		||||
    color: rgba(255, 255, 255, 0.4);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ct-point-label {
 | 
			
		||||
  fill: rgba(255, 255, 255, 1);
 | 
			
		||||
  color: rgba(255, 255, 255, 1);
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ct-grid {
 | 
			
		||||
    stroke: rgba(255, 255, 255, 0.2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* LEGEND */
 | 
			
		||||
 | 
			
		||||
.ct-legend {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  z-index: 10;
 | 
			
		||||
  left: 0px;
 | 
			
		||||
  list-style: none;
 | 
			
		||||
  font-size: 13px;
 | 
			
		||||
  padding: 0px 0px 0px 30px;
 | 
			
		||||
  top: 90px;
 | 
			
		||||
 | 
			
		||||
  li {
 | 
			
		||||
      position: relative;
 | 
			
		||||
      padding-left: 23px;
 | 
			
		||||
      margin-bottom: 0px;
 | 
			
		||||
.tx-wrapper-tooltip-chart, .fees-wrapper-tooltip-chart {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
  background: rgba(#11131f, 0.85);
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  padding: 10px 15px;
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  box-shadow: 1px 1px 10px rgba(0,0,0,0.2);
 | 
			
		||||
  .item {
 | 
			
		||||
    text-align: left;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    .indicator {
 | 
			
		||||
      display: block;
 | 
			
		||||
      margin-right: 5px;
 | 
			
		||||
      border-radius: 10px;
 | 
			
		||||
      margin-top: 5px;
 | 
			
		||||
      width: 9px;
 | 
			
		||||
      height: 9px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  li:before {
 | 
			
		||||
      width: 12px;
 | 
			
		||||
      height: 12px;
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      left: 0;
 | 
			
		||||
      bottom: 3px;
 | 
			
		||||
      content: '';
 | 
			
		||||
      border: 3px solid transparent;
 | 
			
		||||
      border-radius: 2px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  li.inactive:before {
 | 
			
		||||
      background: transparent;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.ct-legend-inside {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      top: 0;
 | 
			
		||||
      right: 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @for $i from 0 to length($ct-series-colors) {
 | 
			
		||||
      .ct-series-#{$i}:before {
 | 
			
		||||
          background-color: nth($ct-series-colors, $i + 1);
 | 
			
		||||
          border-color: nth($ct-series-colors, $i + 1);
 | 
			
		||||
    .value {
 | 
			
		||||
      text-align: right;
 | 
			
		||||
      span {
 | 
			
		||||
        color: #212121 !important;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grow {
 | 
			
		||||
  flex-grow: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chartist-tooltip {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
  min-width: 5em;
 | 
			
		||||
  padding: .5em;
 | 
			
		||||
  background: #F4C63D;
 | 
			
		||||
  color: #453D3F;
 | 
			
		||||
  font-family: Oxygen,Helvetica,Arial,sans-serif;
 | 
			
		||||
  font-weight: 700;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  pointer-events: none;
 | 
			
		||||
  z-index: 1;
 | 
			
		||||
  -webkit-transition: opacity .2s linear;
 | 
			
		||||
  -moz-transition: opacity .2s linear;
 | 
			
		||||
  -o-transition: opacity .2s linear;
 | 
			
		||||
  transition: opacity .2s linear; }
 | 
			
		||||
  .chartist-tooltip:before {
 | 
			
		||||
    content: "";
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 100%;
 | 
			
		||||
    left: 50%;
 | 
			
		||||
    width: 0;
 | 
			
		||||
    height: 0;
 | 
			
		||||
    margin-left: -15px;
 | 
			
		||||
    border: 15px solid transparent;
 | 
			
		||||
    border-top-color: #F4C63D; }
 | 
			
		||||
  .chartist-tooltip.tooltip-show {
 | 
			
		||||
    opacity: 1; }
 | 
			
		||||
.fees-wrapper-tooltip-chart {
 | 
			
		||||
  .item {
 | 
			
		||||
    font-size: 9px;
 | 
			
		||||
    line-height: 1;
 | 
			
		||||
  }
 | 
			
		||||
  .indicator {
 | 
			
		||||
    margin-right: 5px !important;
 | 
			
		||||
    border-radius: 10px !important;
 | 
			
		||||
    margin-top: 0px !important;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ct-area, .ct-line {
 | 
			
		||||
  pointer-events: none; }
 | 
			
		||||
 | 
			
		||||
.ct-bar {
 | 
			
		||||
  stroke-width: 1px;
 | 
			
		||||
.fee-distribution-chart {
 | 
			
		||||
  height: 250px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
hr {
 | 
			
		||||
@ -639,7 +535,7 @@ th {
 | 
			
		||||
  .card {
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
    button {
 | 
			
		||||
      text-align: left;
 | 
			
		||||
      display: block;
 | 
			
		||||
@ -653,17 +549,17 @@ th {
 | 
			
		||||
        box-shadow: none;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
    .card-header {
 | 
			
		||||
      padding: 0;
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
    .collapsed{
 | 
			
		||||
      background-color: #2d3348;
 | 
			
		||||
      color: #1bd8f4;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  .subtitle {
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    margin-bottom: 3px;
 | 
			
		||||
@ -675,7 +571,6 @@ th {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.pagination-container {
 | 
			
		||||
  
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
@ -698,4 +593,4 @@ th {
 | 
			
		||||
  .tooltip.show {
 | 
			
		||||
    width: 220px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user