Merge pull request #4236 from mempool/mononaut/mempool-count-graph
Add mempool transaction count line to graph
This commit is contained in:
		
						commit
						b6f66a6a28
					
				@ -171,6 +171,7 @@ class StatisticsApi {
 | 
			
		||||
  private getQueryForDaysAvg(div: number, interval: string) {
 | 
			
		||||
    return `SELECT
 | 
			
		||||
      UNIX_TIMESTAMP(added) as added,
 | 
			
		||||
      CAST(avg(unconfirmed_transactions) as DOUBLE) as unconfirmed_transactions,
 | 
			
		||||
      CAST(avg(vbytes_per_second) as DOUBLE) as vbytes_per_second,
 | 
			
		||||
      CAST(avg(vsize_1) as DOUBLE) as vsize_1,
 | 
			
		||||
      CAST(avg(vsize_2) as DOUBLE) as vsize_2,
 | 
			
		||||
@ -219,6 +220,7 @@ class StatisticsApi {
 | 
			
		||||
  private getQueryForDays(div: number, interval: string) {
 | 
			
		||||
    return `SELECT
 | 
			
		||||
      UNIX_TIMESTAMP(added) as added,
 | 
			
		||||
      CAST(avg(unconfirmed_transactions) as DOUBLE) as unconfirmed_transactions,
 | 
			
		||||
      CAST(avg(vbytes_per_second) as DOUBLE) as vbytes_per_second,
 | 
			
		||||
      vsize_1,
 | 
			
		||||
      vsize_2,
 | 
			
		||||
@ -401,6 +403,7 @@ class StatisticsApi {
 | 
			
		||||
    return statistic.map((s) => {
 | 
			
		||||
      return {
 | 
			
		||||
        added: s.added,
 | 
			
		||||
        count: s.unconfirmed_transactions,
 | 
			
		||||
        vbytes_per_second: s.vbytes_per_second,
 | 
			
		||||
        mempool_byte_weight: s.mempool_byte_weight,
 | 
			
		||||
        total_fee: s.total_fee,
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
import { Component, OnInit, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnChanges } from '@angular/core';
 | 
			
		||||
import { VbytesPipe } from '../../shared/pipes/bytes-pipe/vbytes.pipe';
 | 
			
		||||
import { WuBytesPipe } from '../../shared/pipes/bytes-pipe/wubytes.pipe';
 | 
			
		||||
import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe';
 | 
			
		||||
import { formatNumber } from '@angular/common';
 | 
			
		||||
import { OptimizedMempoolStats } from '../../interfaces/node-api.interface';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
@ -26,6 +27,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
  @Input() data: any[];
 | 
			
		||||
  @Input() filterSize = 100000;
 | 
			
		||||
  @Input() limitFilterFee = 1;
 | 
			
		||||
  @Input() hideCount: boolean = false;
 | 
			
		||||
  @Input() height: number | string = 200;
 | 
			
		||||
  @Input() top: number | string = 20;
 | 
			
		||||
  @Input() right: number | string = 10;
 | 
			
		||||
@ -50,10 +52,13 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
  inverted: boolean;
 | 
			
		||||
  chartInstance: any = undefined;
 | 
			
		||||
  weightMode: boolean = false;
 | 
			
		||||
  isWidget: boolean = false;
 | 
			
		||||
  showCount: boolean = true;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private vbytesPipe: VbytesPipe,
 | 
			
		||||
    private wubytesPipe: WuBytesPipe,
 | 
			
		||||
    private amountShortenerPipe: AmountShortenerPipe,
 | 
			
		||||
    private stateService: StateService,
 | 
			
		||||
    private storageService: StorageService,
 | 
			
		||||
    @Inject(LOCALE_ID) private locale: string,
 | 
			
		||||
@ -62,12 +67,16 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.isLoading = true;
 | 
			
		||||
    this.inverted = this.storageService.getValue('inverted-graph') === 'true';
 | 
			
		||||
    this.isWidget = this.template === 'widget';
 | 
			
		||||
    this.showCount = !this.isWidget && !this.hideCount;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnChanges() {
 | 
			
		||||
  ngOnChanges(changes) {
 | 
			
		||||
    if (!this.data) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    this.isWidget = this.template === 'widget';
 | 
			
		||||
    this.showCount = !this.isWidget && !this.hideCount;
 | 
			
		||||
    this.windowPreference = this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference');
 | 
			
		||||
    this.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([]));
 | 
			
		||||
    this.mountFeeChart();
 | 
			
		||||
@ -96,10 +105,12 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
    mempoolStats.reverse();
 | 
			
		||||
    const labels = mempoolStats.map(stats => stats.added);
 | 
			
		||||
    const finalArrayVByte = this.generateArray(mempoolStats);
 | 
			
		||||
    const finalArrayCount = this.generateCountArray(mempoolStats);
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      labels: labels,
 | 
			
		||||
      series: finalArrayVByte
 | 
			
		||||
      series: finalArrayVByte,
 | 
			
		||||
      countSeries: finalArrayCount,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -124,9 +135,13 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
    return finalArray;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  generateCountArray(mempoolStats: OptimizedMempoolStats[]) {
 | 
			
		||||
    return mempoolStats.filter(stats => stats.count > 0).map(stats => [stats.added * 1000, stats.count]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  mountFeeChart() {
 | 
			
		||||
    this.orderLevels();
 | 
			
		||||
    const { series } = this.mempoolVsizeFeesData;
 | 
			
		||||
    const { series, countSeries } = this.mempoolVsizeFeesData;
 | 
			
		||||
 | 
			
		||||
    const seriesGraph = [];
 | 
			
		||||
    const newColors = [];
 | 
			
		||||
@ -178,6 +193,29 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (this.showCount) {
 | 
			
		||||
      newColors.push('white');
 | 
			
		||||
      seriesGraph.push({
 | 
			
		||||
        zlevel: 1,
 | 
			
		||||
        yAxisIndex: 1,
 | 
			
		||||
        name: 'count',
 | 
			
		||||
        type: 'line',
 | 
			
		||||
        stack: 'count',
 | 
			
		||||
        smooth: false,
 | 
			
		||||
        markPoint: false,
 | 
			
		||||
        lineStyle: {
 | 
			
		||||
          width: 2,
 | 
			
		||||
          opacity: 1,
 | 
			
		||||
        },
 | 
			
		||||
        symbol: 'none',
 | 
			
		||||
        silent: true,
 | 
			
		||||
        areaStyle: {
 | 
			
		||||
          color: null,
 | 
			
		||||
          opacity: 0,
 | 
			
		||||
        },
 | 
			
		||||
        data: countSeries,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.mempoolVsizeFeesOptions = {
 | 
			
		||||
      series: this.inverted ? [...seriesGraph].reverse() : seriesGraph,
 | 
			
		||||
@ -201,7 +239,11 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
          label: {
 | 
			
		||||
            formatter: (params: any) => {
 | 
			
		||||
              if (params.axisDimension === 'y') {
 | 
			
		||||
                return this.vbytesPipe.transform(params.value, 2, 'vB', 'MvB', true)
 | 
			
		||||
                if (params.axisIndex === 0) {
 | 
			
		||||
                  return this.vbytesPipe.transform(params.value, 2, 'vB', 'MvB', true);
 | 
			
		||||
                } else {
 | 
			
		||||
                  return this.amountShortenerPipe.transform(params.value, 2, undefined, true);
 | 
			
		||||
                }
 | 
			
		||||
              } else {
 | 
			
		||||
                return formatterXAxis(this.locale, this.windowPreference, params.value);
 | 
			
		||||
              }
 | 
			
		||||
@ -214,7 +256,11 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
          const itemFormatted = [];
 | 
			
		||||
          let totalParcial = 0;
 | 
			
		||||
          let progressPercentageText = '';
 | 
			
		||||
          const items = this.inverted ? [...params].reverse() : params;
 | 
			
		||||
          let countItem;
 | 
			
		||||
          let items = this.inverted ? [...params].reverse() : params;
 | 
			
		||||
          if (items[items.length - 1].seriesName === 'count') {
 | 
			
		||||
            countItem = items.pop();
 | 
			
		||||
          }
 | 
			
		||||
          items.map((item: any, index: number) => {
 | 
			
		||||
            totalParcial += item.value[1];
 | 
			
		||||
            const progressPercentage = (item.value[1] / totalValue) * 100;
 | 
			
		||||
@ -276,6 +322,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
            </tr>`);
 | 
			
		||||
          });
 | 
			
		||||
          const classActive = (this.template === 'advanced') ? 'fees-wrapper-tooltip-chart-advanced' : '';
 | 
			
		||||
          const titleCount = $localize`Count`;
 | 
			
		||||
          const titleRange = $localize`Range`;
 | 
			
		||||
          const titleSize = $localize`:@@7faaaa08f56427999f3be41df1093ce4089bbd75:Size`;
 | 
			
		||||
          const titleSum = $localize`Sum`;
 | 
			
		||||
@ -286,6 +333,25 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
                ${this.vbytesPipe.transform(totalValue, 2, 'vB', 'MvB', false)}
 | 
			
		||||
              </span>
 | 
			
		||||
            </div>
 | 
			
		||||
            ` +
 | 
			
		||||
              (this.showCount && countItem ? `
 | 
			
		||||
                <table class="count">
 | 
			
		||||
                  <tbody>
 | 
			
		||||
                    <tr class="item">
 | 
			
		||||
                      <td class="indicator-container">
 | 
			
		||||
                        <span class="indicator" style="background-color: white"></span>
 | 
			
		||||
                        <span>
 | 
			
		||||
                          ${titleCount}
 | 
			
		||||
                        </span>
 | 
			
		||||
                      </td>
 | 
			
		||||
                      <td style="text-align: right;">
 | 
			
		||||
                        <span>${this.amountShortenerPipe.transform(countItem.value[1], 2, undefined, true)}</span>
 | 
			
		||||
                      </td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                  </tbody>
 | 
			
		||||
                </table>
 | 
			
		||||
              ` : '')
 | 
			
		||||
            + `
 | 
			
		||||
            <table>
 | 
			
		||||
              <thead>
 | 
			
		||||
                <tr>
 | 
			
		||||
@ -305,12 +371,12 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
          </div>`;
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      dataZoom: (this.template === 'widget' && this.isMobile()) ? null : [{
 | 
			
		||||
      dataZoom: (this.isWidget && this.isMobile()) ? null : [{
 | 
			
		||||
        type: 'inside',
 | 
			
		||||
        realtime: true,
 | 
			
		||||
        zoomLock: (this.template === 'widget') ? true : false,
 | 
			
		||||
        zoomLock: (this.isWidget) ? true : false,
 | 
			
		||||
        zoomOnMouseWheel: (this.template === 'advanced') ? true : false,
 | 
			
		||||
        moveOnMouseMove: (this.template === 'widget') ? true : false,
 | 
			
		||||
        moveOnMouseMove: (this.isWidget) ? true : false,
 | 
			
		||||
        maxSpan: 100,
 | 
			
		||||
        minSpan: 10,
 | 
			
		||||
      }, {
 | 
			
		||||
@ -339,7 +405,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
      },
 | 
			
		||||
      xAxis: [
 | 
			
		||||
        {
 | 
			
		||||
          name: this.template === 'widget' ? '' : formatterXAxisLabel(this.locale, this.windowPreference),
 | 
			
		||||
          name: this.isWidget ? '' : formatterXAxisLabel(this.locale, this.windowPreference),
 | 
			
		||||
          nameLocation: 'middle',
 | 
			
		||||
          nameTextStyle: {
 | 
			
		||||
            padding: [20, 0, 0, 0],
 | 
			
		||||
@ -357,7 +423,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
          },
 | 
			
		||||
        }
 | 
			
		||||
      ],
 | 
			
		||||
      yAxis: {
 | 
			
		||||
      yAxis: [{
 | 
			
		||||
        type: 'value',
 | 
			
		||||
        axisLine: { onZero: false },
 | 
			
		||||
        axisLabel: {
 | 
			
		||||
@ -371,7 +437,17 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
 | 
			
		||||
            opacity: 0.25,
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      }, this.showCount ? {
 | 
			
		||||
        type: 'value',
 | 
			
		||||
        position: 'right',
 | 
			
		||||
        axisLine: { onZero: false },
 | 
			
		||||
        axisLabel: {
 | 
			
		||||
          formatter: (value: number) => (`${this.amountShortenerPipe.transform(value, 2, undefined, true)}`),
 | 
			
		||||
        },
 | 
			
		||||
        splitLine: {
 | 
			
		||||
          show: false,
 | 
			
		||||
        }
 | 
			
		||||
      } : null],
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -69,6 +69,12 @@
 | 
			
		||||
                </button>
 | 
			
		||||
                <div class="dropdown-fees" ngbDropdownMenu aria-labelledby="dropdownFees">
 | 
			
		||||
                  <ul>
 | 
			
		||||
                    <li (click)="this.showCount = !this.showCount"
 | 
			
		||||
                      [class]="this.showCount ? '' : 'inactive'">
 | 
			
		||||
                      <span class="square" [ngStyle]="{'backgroundColor': 'white'}"></span>
 | 
			
		||||
                      <span class="fee-text">{{ titleCount }}</span>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <hr style="margin: 4px;">
 | 
			
		||||
                    <ng-template ngFor let-feeData let-i="index" [ngForOf]="feeLevelDropdownData">
 | 
			
		||||
                      <ng-template [ngIf]="feeData.fee <= (feeLevels[maxFeeIndex])">
 | 
			
		||||
                        <li (click)="filterFeeIndex = feeData.fee"
 | 
			
		||||
@ -92,8 +98,8 @@
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
          <div class="incoming-transactions-graph">
 | 
			
		||||
            <app-mempool-graph #mempoolgraph dir="ltr" [template]="'advanced'"
 | 
			
		||||
              [limitFilterFee]="filterFeeIndex" [height]="500" [left]="65" [right]="10"
 | 
			
		||||
            <app-mempool-graph #mempoolgraph dir="ltr" [template]="'advanced'" [hideCount]="!showCount"
 | 
			
		||||
              [limitFilterFee]="filterFeeIndex" [height]="500" [left]="65" [right]="showCount ? 50 : 10"
 | 
			
		||||
              [data]="mempoolStats && mempoolStats.length ? mempoolStats : null"></app-mempool-graph>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
@ -32,6 +32,7 @@ export class StatisticsComponent implements OnInit {
 | 
			
		||||
  chartColors = chartColors;
 | 
			
		||||
  filterSize = 100000;
 | 
			
		||||
  filterFeeIndex = 1;
 | 
			
		||||
  showCount = true;
 | 
			
		||||
  maxFeeIndex: number;
 | 
			
		||||
  dropDownOpen = false;
 | 
			
		||||
 | 
			
		||||
@ -46,6 +47,7 @@ export class StatisticsComponent implements OnInit {
 | 
			
		||||
  inverted: boolean;
 | 
			
		||||
  feeLevelDropdownData = [];
 | 
			
		||||
  timespan = '';
 | 
			
		||||
  titleCount = $localize`Count`;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    @Inject(LOCALE_ID) private locale: string,
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ import { Block, Transaction } from "./electrs.interface";
 | 
			
		||||
 | 
			
		||||
export interface OptimizedMempoolStats {
 | 
			
		||||
  added: number;
 | 
			
		||||
  count: number;
 | 
			
		||||
  vbytes_per_second: number;
 | 
			
		||||
  total_fee: number;
 | 
			
		||||
  mempool_byte_weight: number;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user