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