Merge pull request #1004 from jorisvial/bug/improve-x-axis-intervals
Improve x-axis labels and graph data ticks
This commit is contained in:
		
						commit
						e67f552fbd
					
				@ -267,8 +267,57 @@ class Statistics {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private getQueryForDays(div: number) {
 | 
			
		||||
    return `SELECT id, added, unconfirmed_transactions,
 | 
			
		||||
  private getQueryForDaysAvg(div: number, interval: string) {
 | 
			
		||||
    return `SELECT id, UNIX_TIMESTAMP(added) as added,
 | 
			
		||||
      CAST(avg(unconfirmed_transactions) as FLOAT) as unconfirmed_transactions,
 | 
			
		||||
      CAST(avg(tx_per_second) as FLOAT) as tx_per_second,
 | 
			
		||||
      CAST(avg(vbytes_per_second) as FLOAT) as vbytes_per_second,
 | 
			
		||||
      CAST(avg(vsize_1) as FLOAT) as vsize_1,
 | 
			
		||||
      CAST(avg(vsize_2) as FLOAT) as vsize_2,
 | 
			
		||||
      CAST(avg(vsize_3) as FLOAT) as vsize_3,
 | 
			
		||||
      CAST(avg(vsize_4) as FLOAT) as vsize_4,
 | 
			
		||||
      CAST(avg(vsize_5) as FLOAT) as vsize_5,
 | 
			
		||||
      CAST(avg(vsize_6) as FLOAT) as vsize_6,
 | 
			
		||||
      CAST(avg(vsize_8) as FLOAT) as vsize_8,
 | 
			
		||||
      CAST(avg(vsize_10) as FLOAT) as vsize_10,
 | 
			
		||||
      CAST(avg(vsize_12) as FLOAT) as vsize_12,
 | 
			
		||||
      CAST(avg(vsize_15) as FLOAT) as vsize_15,
 | 
			
		||||
      CAST(avg(vsize_20) as FLOAT) as vsize_20,
 | 
			
		||||
      CAST(avg(vsize_30) as FLOAT) as vsize_30,
 | 
			
		||||
      CAST(avg(vsize_40) as FLOAT) as vsize_40,
 | 
			
		||||
      CAST(avg(vsize_50) as FLOAT) as vsize_50,
 | 
			
		||||
      CAST(avg(vsize_60) as FLOAT) as vsize_60,
 | 
			
		||||
      CAST(avg(vsize_70) as FLOAT) as vsize_70,
 | 
			
		||||
      CAST(avg(vsize_80) as FLOAT) as vsize_80,
 | 
			
		||||
      CAST(avg(vsize_90) as FLOAT) as vsize_90,
 | 
			
		||||
      CAST(avg(vsize_100) as FLOAT) as vsize_100,
 | 
			
		||||
      CAST(avg(vsize_125) as FLOAT) as vsize_125,
 | 
			
		||||
      CAST(avg(vsize_150) as FLOAT) as vsize_150,
 | 
			
		||||
      CAST(avg(vsize_175) as FLOAT) as vsize_175,
 | 
			
		||||
      CAST(avg(vsize_200) as FLOAT) as vsize_200,
 | 
			
		||||
      CAST(avg(vsize_250) as FLOAT) as vsize_250,
 | 
			
		||||
      CAST(avg(vsize_300) as FLOAT) as vsize_300,
 | 
			
		||||
      CAST(avg(vsize_350) as FLOAT) as vsize_350,
 | 
			
		||||
      CAST(avg(vsize_400) as FLOAT) as vsize_400,
 | 
			
		||||
      CAST(avg(vsize_500) as FLOAT) as vsize_500,
 | 
			
		||||
      CAST(avg(vsize_600) as FLOAT) as vsize_600,
 | 
			
		||||
      CAST(avg(vsize_700) as FLOAT) as vsize_700,
 | 
			
		||||
      CAST(avg(vsize_800) as FLOAT) as vsize_800,
 | 
			
		||||
      CAST(avg(vsize_900) as FLOAT) as vsize_900,
 | 
			
		||||
      CAST(avg(vsize_1000) as FLOAT) as vsize_1000,
 | 
			
		||||
      CAST(avg(vsize_1200) as FLOAT) as vsize_1200,
 | 
			
		||||
      CAST(avg(vsize_1400) as FLOAT) as vsize_1400,
 | 
			
		||||
      CAST(avg(vsize_1600) as FLOAT) as vsize_1600,
 | 
			
		||||
      CAST(avg(vsize_1800) as FLOAT) as vsize_1800,
 | 
			
		||||
      CAST(avg(vsize_2000) as FLOAT) as vsize_2000 \
 | 
			
		||||
      FROM statistics \
 | 
			
		||||
      WHERE added BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW() \
 | 
			
		||||
      GROUP BY UNIX_TIMESTAMP(added) DIV ${div} \
 | 
			
		||||
      ORDER BY id DESC;`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private getQueryForDays(div: number, interval: string) {
 | 
			
		||||
    return `SELECT id, UNIX_TIMESTAMP(added) as added, unconfirmed_transactions,
 | 
			
		||||
      tx_per_second,
 | 
			
		||||
      vbytes_per_second,
 | 
			
		||||
      vsize_1,
 | 
			
		||||
@ -308,13 +357,17 @@ class Statistics {
 | 
			
		||||
      vsize_1400,
 | 
			
		||||
      vsize_1600,
 | 
			
		||||
      vsize_1800,
 | 
			
		||||
      vsize_2000 FROM statistics GROUP BY UNIX_TIMESTAMP(added) DIV ${div} ORDER BY id DESC LIMIT 480`;
 | 
			
		||||
      vsize_2000 \
 | 
			
		||||
      FROM statistics \
 | 
			
		||||
      WHERE added BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW() \
 | 
			
		||||
      GROUP BY UNIX_TIMESTAMP(added) DIV ${div} \
 | 
			
		||||
      ORDER BY id DESC;`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $get(id: number): Promise<OptimizedStatistic | undefined> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.pool.getConnection();
 | 
			
		||||
      const query = `SELECT * FROM statistics WHERE id = ?`;
 | 
			
		||||
      const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics WHERE id = ?`;
 | 
			
		||||
      const [rows] = await connection.query<any>(query, [id]);
 | 
			
		||||
      connection.release();
 | 
			
		||||
      if (rows[0]) {
 | 
			
		||||
@ -328,7 +381,7 @@ class Statistics {
 | 
			
		||||
  public async $list2H(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.pool.getConnection();
 | 
			
		||||
      const query = `SELECT * FROM statistics ORDER BY id DESC LIMIT 120`;
 | 
			
		||||
      const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics ORDER BY id DESC LIMIT 120`;
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
@ -341,7 +394,7 @@ class Statistics {
 | 
			
		||||
  public async $list24H(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.pool.getConnection();
 | 
			
		||||
      const query = this.getQueryForDays(180);
 | 
			
		||||
      const query = this.getQueryForDaysAvg(120, '1 DAY'); // 2m interval
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
@ -354,7 +407,7 @@ class Statistics {
 | 
			
		||||
  public async $list1W(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.pool.getConnection();
 | 
			
		||||
      const query = this.getQueryForDays(1260);
 | 
			
		||||
      const query = this.getQueryForDaysAvg(600, '1 WEEK'); // 10m interval
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
@ -367,7 +420,7 @@ class Statistics {
 | 
			
		||||
  public async $list1M(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.pool.getConnection();
 | 
			
		||||
      const query = this.getQueryForDays(5040);
 | 
			
		||||
      const query = this.getQueryForDaysAvg(3600, '1 MONTH'); // 1h interval
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
@ -380,7 +433,7 @@ class Statistics {
 | 
			
		||||
  public async $list3M(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.pool.getConnection();
 | 
			
		||||
      const query = this.getQueryForDays(15120);
 | 
			
		||||
      const query = this.getQueryForDaysAvg(14400, '3 MONTH'); // 4h interval
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
@ -393,7 +446,7 @@ class Statistics {
 | 
			
		||||
  public async $list6M(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.pool.getConnection();
 | 
			
		||||
      const query = this.getQueryForDays(30240);
 | 
			
		||||
      const query = this.getQueryForDaysAvg(21600, '6 MONTH'); // 6h interval 
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
@ -406,7 +459,7 @@ class Statistics {
 | 
			
		||||
  public async $list1Y(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.pool.getConnection();
 | 
			
		||||
      const query = this.getQueryForDays(60480);
 | 
			
		||||
      const query = this.getQueryForDays(43200, '1 YEAR'); // 12h interval
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
@ -419,7 +472,7 @@ class Statistics {
 | 
			
		||||
  public async $list2Y(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.pool.getConnection();
 | 
			
		||||
      const query = this.getQueryForDays(120960);
 | 
			
		||||
      const query = this.getQueryForDays(86400, "2 YEAR"); // 1d interval
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
@ -432,7 +485,7 @@ class Statistics {
 | 
			
		||||
  public async $list3Y(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.pool.getConnection();
 | 
			
		||||
      const query = this.getQueryForDays(181440);
 | 
			
		||||
      const query = this.getQueryForDays(86400, "3 YEAR"); // 1d interval
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,6 @@ import { BlockchainComponent } from './components/blockchain/blockchain.componen
 | 
			
		||||
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';
 | 
			
		||||
@ -84,7 +83,6 @@ import { PushTransactionComponent } from './components/push-transaction/push-tra
 | 
			
		||||
    MempoolBlocksComponent,
 | 
			
		||||
    FooterComponent,
 | 
			
		||||
    MempoolBlockComponent,
 | 
			
		||||
    FeeDistributionGraphComponent,
 | 
			
		||||
    IncomingTransactionsGraphComponent,
 | 
			
		||||
    MempoolGraphComponent,
 | 
			
		||||
    LbtcPegsGraphComponent,
 | 
			
		||||
 | 
			
		||||
@ -1,9 +0,0 @@
 | 
			
		||||
<div class="fee-distribution-chart" *ngIf="mempoolVsizeFeesOptions; else loadingFees">
 | 
			
		||||
  <div echarts [initOpts]="mempoolVsizeFeesInitOptions" [options]="mempoolVsizeFeesOptions"></div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<ng-template #loadingFees>
 | 
			
		||||
  <div class="text-center">
 | 
			
		||||
    <div class="spinner-border text-light"></div>
 | 
			
		||||
  </div>
 | 
			
		||||
</ng-template>
 | 
			
		||||
@ -1,83 +0,0 @@
 | 
			
		||||
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',
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
  mempoolVsizeFeesOptions: any;
 | 
			
		||||
  mempoolVsizeFeesInitOptions = {
 | 
			
		||||
    renderer: 'svg'
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  constructor() { }
 | 
			
		||||
 | 
			
		||||
  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,
 | 
			
		||||
        }
 | 
			
		||||
      }]
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
import { Component, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnInit } from '@angular/core';
 | 
			
		||||
import { formatDate } from '@angular/common';
 | 
			
		||||
import { EChartsOption } from 'echarts';
 | 
			
		||||
import { OnChanges } from '@angular/core';
 | 
			
		||||
import { StorageService } from 'src/app/services/storage.service';
 | 
			
		||||
import { formatterXAxis, formatterXAxisLabel } from 'src/app/shared/graphs.utils';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-incoming-transactions-graph',
 | 
			
		||||
@ -25,6 +25,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
  @Input() top: number | string = '20';
 | 
			
		||||
  @Input() left: number | string = '0';
 | 
			
		||||
  @Input() template: ('widget' | 'advanced') = 'widget';
 | 
			
		||||
  @Input() windowPreferenceOverride: string;
 | 
			
		||||
 | 
			
		||||
  isLoading = true;
 | 
			
		||||
  mempoolStatsChartOption: EChartsOption = {};
 | 
			
		||||
@ -46,7 +47,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
    if (!this.data) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    this.windowPreference = this.storageService.getValue('graphWindowPreference');
 | 
			
		||||
    this.windowPreference = this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference');
 | 
			
		||||
    this.mountChart();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -73,10 +74,12 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
        maxSpan: 100,
 | 
			
		||||
        minSpan: 10,
 | 
			
		||||
      }, {
 | 
			
		||||
        showDetail: false,
 | 
			
		||||
        show: (this.template === 'advanced') ? true : false,
 | 
			
		||||
        type: 'slider',
 | 
			
		||||
        brushSelect: false,
 | 
			
		||||
        realtime: true,
 | 
			
		||||
        bottom: 0,
 | 
			
		||||
        selectedDataBackground: {
 | 
			
		||||
          lineStyle: {
 | 
			
		||||
            color: '#fff',
 | 
			
		||||
@ -85,7 +88,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
          areaStyle: {
 | 
			
		||||
            opacity: 0,
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        },
 | 
			
		||||
      }],
 | 
			
		||||
      tooltip: {
 | 
			
		||||
        trigger: 'axis',
 | 
			
		||||
@ -102,29 +105,39 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
          type: 'line',
 | 
			
		||||
        },
 | 
			
		||||
        formatter: (params: any) => {
 | 
			
		||||
          const axisValueLabel: string = formatterXAxis(this.locale, this.windowPreference, params[0].axisValue);         
 | 
			
		||||
          const colorSpan = (color: string) => `<span class="indicator" style="background-color: ` + color + `"></span>`;
 | 
			
		||||
          let itemFormatted = '<div class="title">' + params[0].axisValue + '</div>';
 | 
			
		||||
          let itemFormatted = '<div class="title">' + axisValueLabel + '</div>';
 | 
			
		||||
          params.map((item: any, index: number) => {
 | 
			
		||||
            if (index < 26) {
 | 
			
		||||
              itemFormatted += `<div class="item">
 | 
			
		||||
                <div class="indicator-container">${colorSpan(item.color)}</div>
 | 
			
		||||
                <div class="grow"></div>
 | 
			
		||||
                <div class="value">${item.value} <span class="symbol">vB/s</span></div>
 | 
			
		||||
                <div class="value">${item.value[1]} <span class="symbol">vB/s</span></div>
 | 
			
		||||
              </div>`;
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
          return `<div class="tx-wrapper-tooltip-chart ${(this.template === 'advanced') ? 'tx-wrapper-tooltip-chart-advanced' : ''}">${itemFormatted}</div>`;
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      xAxis: {
 | 
			
		||||
        type: 'category',
 | 
			
		||||
      xAxis: [
 | 
			
		||||
        {
 | 
			
		||||
          name: formatterXAxisLabel(this.locale, this.windowPreference),
 | 
			
		||||
          nameLocation: 'middle',
 | 
			
		||||
          nameTextStyle: {
 | 
			
		||||
            padding: [20, 0, 0, 0],
 | 
			
		||||
          },
 | 
			
		||||
          type: 'time',
 | 
			
		||||
          axisLabel: {
 | 
			
		||||
            margin: 20,
 | 
			
		||||
            align: 'center',
 | 
			
		||||
            fontSize: 11,
 | 
			
		||||
          lineHeight: 12
 | 
			
		||||
        },
 | 
			
		||||
        data: this.data.labels.map((value: any) => `${formatDate(value, 'M/d', this.locale)}\n${formatDate(value, 'H:mm', this.locale)}`),
 | 
			
		||||
            lineHeight: 12,
 | 
			
		||||
            hideOverlap: true,
 | 
			
		||||
            padding: [0, 5],
 | 
			
		||||
          },
 | 
			
		||||
        }
 | 
			
		||||
      ],
 | 
			
		||||
      yAxis: {
 | 
			
		||||
        type: 'value',
 | 
			
		||||
        axisLabel: {
 | 
			
		||||
 | 
			
		||||
@ -40,9 +40,6 @@
 | 
			
		||||
          </tbody>
 | 
			
		||||
        </table>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="col-md chart-container">
 | 
			
		||||
        <app-fee-distribution-graph [data]="mempoolBlock.feeRange" ></app-fee-distribution-graph>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,12 @@
 | 
			
		||||
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 { formatNumber } from "@angular/common";
 | 
			
		||||
 | 
			
		||||
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';
 | 
			
		||||
import { formatterXAxis, formatterXAxisLabel } from 'src/app/shared/graphs.utils';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-mempool-graph',
 | 
			
		||||
@ -32,6 +31,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
  @Input() left: number | string = 75;
 | 
			
		||||
  @Input() template: ('widget' | 'advanced') = 'widget';
 | 
			
		||||
  @Input() showZoom = true;
 | 
			
		||||
  @Input() windowPreferenceOverride: string;
 | 
			
		||||
 | 
			
		||||
  isLoading = true;
 | 
			
		||||
  mempoolVsizeFeesData: any;
 | 
			
		||||
@ -62,7 +62,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
    if (!this.data) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    this.windowPreference = this.storageService.getValue('graphWindowPreference');
 | 
			
		||||
    this.windowPreference = this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference');
 | 
			
		||||
    this.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([]));
 | 
			
		||||
    this.mountFeeChart();
 | 
			
		||||
  }
 | 
			
		||||
@ -97,13 +97,13 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  generateArray(mempoolStats: OptimizedMempoolStats[]) {
 | 
			
		||||
    const finalArray: number[][] = [];
 | 
			
		||||
    let feesArray: number[] = [];
 | 
			
		||||
    const finalArray: number[][][] = [];
 | 
			
		||||
    let feesArray: number[][] = [];
 | 
			
		||||
    let limitFeesTemplate = this.template === 'advanced' ? 26 : 20;
 | 
			
		||||
    for (let index = limitFeesTemplate; index > -1; index--) {
 | 
			
		||||
      feesArray = [];
 | 
			
		||||
      mempoolStats.forEach((stats) => {
 | 
			
		||||
        feesArray.push(stats.vsizes[index] ? stats.vsizes[index] : 0);
 | 
			
		||||
        feesArray.push([stats.added * 1000, stats.vsizes[index] ? stats.vsizes[index] : 0]);
 | 
			
		||||
      });
 | 
			
		||||
      finalArray.push(feesArray);
 | 
			
		||||
    }
 | 
			
		||||
@ -113,7 +113,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
 | 
			
		||||
  mountFeeChart() {
 | 
			
		||||
    this.orderLevels();
 | 
			
		||||
    const { labels, series } = this.mempoolVsizeFeesData;
 | 
			
		||||
    const { series } = this.mempoolVsizeFeesData;
 | 
			
		||||
 | 
			
		||||
    const seriesGraph = [];
 | 
			
		||||
    const newColors = [];
 | 
			
		||||
@ -186,14 +186,15 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
          type: 'line',
 | 
			
		||||
        },
 | 
			
		||||
        formatter: (params: any) => {
 | 
			
		||||
          const axisValueLabel: string = formatterXAxis(this.locale, this.windowPreference, params[0].axisValue);         
 | 
			
		||||
          const { totalValue, totalValueArray } = this.getTotalValues(params);
 | 
			
		||||
          const itemFormatted = [];
 | 
			
		||||
          let totalParcial = 0;
 | 
			
		||||
          let progressPercentageText = '';
 | 
			
		||||
          const items = this.inverted ? [...params].reverse() : params;
 | 
			
		||||
          items.map((item: any, index: number) => {
 | 
			
		||||
            totalParcial += item.value;
 | 
			
		||||
            const progressPercentage = (item.value / totalValue) * 100;
 | 
			
		||||
            totalParcial += item.value[1];
 | 
			
		||||
            const progressPercentage = (item.value[1] / totalValue) * 100;
 | 
			
		||||
            const progressPercentageSum = (totalValueArray[index] / totalValue) * 100;
 | 
			
		||||
            let activeItemClass = '';
 | 
			
		||||
            let hoverActive = 0;
 | 
			
		||||
@ -233,7 +234,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
              </td>
 | 
			
		||||
              <td class="total-progress-sum">
 | 
			
		||||
                <span>
 | 
			
		||||
                  ${this.vbytesPipe.transform(item.value, 2, 'vB', 'MvB', false)}
 | 
			
		||||
                  ${this.vbytesPipe.transform(item.value[1], 2, 'vB', 'MvB', false)}
 | 
			
		||||
                </span>
 | 
			
		||||
              </td>
 | 
			
		||||
              <td class="total-progress-sum">
 | 
			
		||||
@ -257,7 +258,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
          const titleSum = $localize`Sum`;
 | 
			
		||||
          return `<div class="fees-wrapper-tooltip-chart ${classActive}">
 | 
			
		||||
            <div class="title">
 | 
			
		||||
              ${params[0].axisValue}
 | 
			
		||||
              ${axisValueLabel}
 | 
			
		||||
              <span class="total-value">
 | 
			
		||||
                ${this.vbytesPipe.transform(totalValue, 2, 'vB', 'MvB', false)}
 | 
			
		||||
              </span>
 | 
			
		||||
@ -288,6 +289,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
        maxSpan: 100,
 | 
			
		||||
        minSpan: 10,
 | 
			
		||||
      }, {
 | 
			
		||||
        showDetail: false,
 | 
			
		||||
        show: (this.template === 'advanced' && this.showZoom) ? true : false,
 | 
			
		||||
        type: 'slider',
 | 
			
		||||
        brushSelect: false,
 | 
			
		||||
@ -312,15 +314,22 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
      },
 | 
			
		||||
      xAxis: [
 | 
			
		||||
        {
 | 
			
		||||
          type: 'category',
 | 
			
		||||
          name: formatterXAxisLabel(this.locale, this.windowPreference),
 | 
			
		||||
          nameLocation: 'middle',
 | 
			
		||||
          nameTextStyle: {
 | 
			
		||||
            padding: [20, 0, 0, 0],
 | 
			
		||||
          },
 | 
			
		||||
          type: 'time',
 | 
			
		||||
          boundaryGap: false,
 | 
			
		||||
          axisLine: { onZero: true },
 | 
			
		||||
          axisLabel: {
 | 
			
		||||
            margin: 20,
 | 
			
		||||
            align: 'center',
 | 
			
		||||
            fontSize: 11,
 | 
			
		||||
            lineHeight: 12,
 | 
			
		||||
            hideOverlap: true,
 | 
			
		||||
            padding: [0, 5],
 | 
			
		||||
          },
 | 
			
		||||
          data: labels.map((value: any) => `${formatDate(value, 'M/d', this.locale)}\n${formatDate(value, 'H:mm', this.locale)}`),
 | 
			
		||||
        }
 | 
			
		||||
      ],
 | 
			
		||||
      yAxis: {
 | 
			
		||||
@ -346,7 +355,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
    const totalValueArray = [];
 | 
			
		||||
    const valuesInverted = this.inverted ? values : [...values].reverse();
 | 
			
		||||
    for (const item of valuesInverted) {
 | 
			
		||||
      totalValueTemp += item.value;
 | 
			
		||||
      totalValueTemp += item.value[1];
 | 
			
		||||
      totalValueArray.push(totalValueTemp);
 | 
			
		||||
    }
 | 
			
		||||
    return {
 | 
			
		||||
 | 
			
		||||
@ -134,7 +134,7 @@ export class StatisticsComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
    this.mempoolTransactionsWeightPerSecondData = {
 | 
			
		||||
      labels: labels,
 | 
			
		||||
      series: [mempoolStats.map((stats) => stats.vbytes_per_second)],
 | 
			
		||||
      series: [mempoolStats.map((stats) => [stats.added * 1000, stats.vbytes_per_second])],
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -54,6 +54,7 @@
 | 
			
		||||
                [limitFee]="150"
 | 
			
		||||
                [limitFilterFee]="1"
 | 
			
		||||
                [data]="mempoolStats.value?.mempool"
 | 
			
		||||
                [windowPreferenceOverride]="'2h'"
 | 
			
		||||
                ></app-mempool-graph>
 | 
			
		||||
              </div>
 | 
			
		||||
            </ng-container>
 | 
			
		||||
@ -73,6 +74,7 @@
 | 
			
		||||
                <app-incoming-transactions-graph
 | 
			
		||||
                  [left]="50"
 | 
			
		||||
                  [data]="mempoolStats.value?.weightPerSecond"
 | 
			
		||||
                  [windowPreferenceOverride]="'2h'"
 | 
			
		||||
                  ></app-incoming-transactions-graph>
 | 
			
		||||
              </div>
 | 
			
		||||
            </ng-template>
 | 
			
		||||
 | 
			
		||||
@ -254,7 +254,6 @@ export class DashboardComponent implements OnInit {
 | 
			
		||||
          );
 | 
			
		||||
        }),
 | 
			
		||||
        map((mempoolStats) => {
 | 
			
		||||
          const data = this.handleNewMempoolData(mempoolStats.concat([]));
 | 
			
		||||
          return {
 | 
			
		||||
            mempool: mempoolStats,
 | 
			
		||||
            weightPerSecond: this.handleNewMempoolData(mempoolStats.concat([])),
 | 
			
		||||
@ -286,7 +285,7 @@ export class DashboardComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      labels: labels,
 | 
			
		||||
      series: [mempoolStats.map((stats) => stats.vbytes_per_second)],
 | 
			
		||||
      series: [mempoolStats.map((stats) => [stats.added * 1000, stats.vbytes_per_second])],
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
export interface OptimizedMempoolStats {
 | 
			
		||||
  id: number;
 | 
			
		||||
  added: string;
 | 
			
		||||
  added: number;
 | 
			
		||||
  unconfirmed_transactions: number;
 | 
			
		||||
  tx_per_second: number;
 | 
			
		||||
  vbytes_per_second: number;
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,25 @@
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { Router, ActivatedRoute } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
@Injectable({
 | 
			
		||||
  providedIn: 'root'
 | 
			
		||||
})
 | 
			
		||||
export class StorageService {
 | 
			
		||||
  constructor(private router: Router, private route: ActivatedRoute) {
 | 
			
		||||
    let graphWindowPreference: string = this.getValue('graphWindowPreference');
 | 
			
		||||
    if (graphWindowPreference === null) { // First visit to mempool.space
 | 
			
		||||
      if (this.router.url.includes("graphs")) {
 | 
			
		||||
        this.setValue('graphWindowPreference', this.route.snapshot.fragment ? this.route.snapshot.fragment : "2h");
 | 
			
		||||
      } else {
 | 
			
		||||
        this.setValue('graphWindowPreference', "2h");
 | 
			
		||||
      }
 | 
			
		||||
    } else if (this.router.url.includes("graphs")) { // Visit a different graphs#fragment from last visit
 | 
			
		||||
        if (this.route.snapshot.fragment !== null && graphWindowPreference !== this.route.snapshot.fragment) {
 | 
			
		||||
          this.setValue('graphWindowPreference', this.route.snapshot.fragment);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getValue(key: string): string {
 | 
			
		||||
    try {
 | 
			
		||||
      return localStorage.getItem(key);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										50
									
								
								frontend/src/app/shared/graphs.utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								frontend/src/app/shared/graphs.utils.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
			
		||||
export const formatterXAxis = (
 | 
			
		||||
  locale: string,
 | 
			
		||||
  windowPreference: string,
 | 
			
		||||
  value: string
 | 
			
		||||
) => {
 | 
			
		||||
 | 
			
		||||
  if(value.length === 0){
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const date = new Date(value);
 | 
			
		||||
  switch (windowPreference) {
 | 
			
		||||
    case '2h':
 | 
			
		||||
      return date.toLocaleTimeString(locale, { hour: 'numeric', minute: 'numeric' });
 | 
			
		||||
    case '24h':
 | 
			
		||||
      return date.toLocaleTimeString(locale, { weekday: 'short', hour: 'numeric', minute: 'numeric' });
 | 
			
		||||
    case '1w':
 | 
			
		||||
      return date.toLocaleTimeString(locale, { month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric' });
 | 
			
		||||
    case '1m':
 | 
			
		||||
    case '3m':
 | 
			
		||||
    case '6m':
 | 
			
		||||
    case '1y':
 | 
			
		||||
      return date.toLocaleTimeString(locale, { month: 'short', day: 'numeric', hour: 'numeric' });
 | 
			
		||||
    case '2y':
 | 
			
		||||
    case '3y':
 | 
			
		||||
      return date.toLocaleDateString(locale, { year: 'numeric', month: 'short', day: 'numeric' });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const formatterXAxisLabel = (
 | 
			
		||||
  locale: string,
 | 
			
		||||
  windowPreference: string,
 | 
			
		||||
) => {
 | 
			
		||||
  const date = new Date();
 | 
			
		||||
  switch (windowPreference) {
 | 
			
		||||
    case '2h':
 | 
			
		||||
    case '24h':
 | 
			
		||||
      return date.toLocaleDateString(locale, { year: 'numeric', month: 'short', day: 'numeric' });
 | 
			
		||||
    case '1w':
 | 
			
		||||
      return date.toLocaleDateString(locale, { year: 'numeric', month: 'long' });
 | 
			
		||||
    case '1m':
 | 
			
		||||
    case '3m':
 | 
			
		||||
    case '6m':
 | 
			
		||||
      return date.toLocaleDateString(locale, { year: 'numeric' });
 | 
			
		||||
    case '1y':
 | 
			
		||||
    case '2y':
 | 
			
		||||
    case '3y':
 | 
			
		||||
      return null;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
@ -555,7 +555,7 @@ html:lang(ru) .card-title {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tx-wrapper-tooltip-chart-advanced {
 | 
			
		||||
  width: 115px;
 | 
			
		||||
  width: 140px;
 | 
			
		||||
  .indicator-container {
 | 
			
		||||
    .indicator {
 | 
			
		||||
      margin-right: 5px;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user