2020-03-29 18:59:04 +02:00
|
|
|
import { Component, OnInit, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnChanges } from '@angular/core';
|
|
|
|
import { formatDate } from '@angular/common';
|
2020-07-03 18:45:19 +02:00
|
|
|
import { VbytesPipe } from 'src/app/shared/pipes/bytes-pipe/vbytes.pipe';
|
2020-03-29 18:59:04 +02:00
|
|
|
import { OptimizedMempoolStats } from 'src/app/interfaces/node-api.interface';
|
2020-05-09 15:37:50 +02:00
|
|
|
import { StateService } from 'src/app/services/state.service';
|
2020-11-16 13:27:06 +01:00
|
|
|
import { StorageService } from 'src/app/services/storage.service';
|
2021-08-21 06:46:28 +02:00
|
|
|
import { EChartsOption } from 'echarts';
|
|
|
|
import { feeLevels, chartColors } from 'src/app/app.constants';
|
|
|
|
|
2020-03-29 18:59:04 +02:00
|
|
|
@Component({
|
|
|
|
selector: 'app-mempool-graph',
|
|
|
|
templateUrl: './mempool-graph.component.html',
|
|
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
|
|
})
|
|
|
|
export class MempoolGraphComponent implements OnInit, OnChanges {
|
2021-08-21 06:46:28 +02:00
|
|
|
@Input() data: any[];
|
2021-08-27 04:30:57 +02:00
|
|
|
@Input() limitFee = 350;
|
2021-08-21 06:46:28 +02:00
|
|
|
@Input() height: number | string = 200;
|
|
|
|
@Input() top: number | string = 20;
|
|
|
|
@Input() right: number | string = 10;
|
|
|
|
@Input() left: number | string = 75;
|
2021-08-27 04:30:57 +02:00
|
|
|
@Input() template: ('widget' | 'advanced') = 'widget';
|
|
|
|
@Input() showZoom = true;
|
2020-03-29 18:59:04 +02:00
|
|
|
|
|
|
|
mempoolVsizeFeesData: any;
|
2021-08-21 06:46:28 +02:00
|
|
|
mempoolVsizeFeesOptions: EChartsOption;
|
2021-09-07 22:48:43 +02:00
|
|
|
mempoolVsizeFeesInitOptions = {
|
|
|
|
renderer: 'svg',
|
|
|
|
};
|
2021-08-25 06:01:35 +02:00
|
|
|
windowPreference: string;
|
2021-09-13 16:07:17 +02:00
|
|
|
hoverIndexSerie = 0;
|
2021-08-27 04:30:57 +02:00
|
|
|
feeLimitIndex: number;
|
2021-09-13 16:07:17 +02:00
|
|
|
feeLevelsOrdered = [];
|
2020-09-23 10:49:07 +02:00
|
|
|
|
2020-03-29 18:59:04 +02:00
|
|
|
constructor(
|
|
|
|
private vbytesPipe: VbytesPipe,
|
2020-05-09 15:37:50 +02:00
|
|
|
private stateService: StateService,
|
2021-08-25 06:01:35 +02:00
|
|
|
private storageService: StorageService,
|
2020-03-29 18:59:04 +02:00
|
|
|
@Inject(LOCALE_ID) private locale: string,
|
|
|
|
) { }
|
|
|
|
|
|
|
|
ngOnInit(): void {
|
2021-09-13 16:07:17 +02:00
|
|
|
this.feeLevelsOrdered = feeLevels.map((sat, i, arr) => {
|
|
|
|
if (arr[i] === this.limitFee) { this.feeLimitIndex = i; }
|
|
|
|
if (arr[i] < this.limitFee) {
|
|
|
|
if (i === 0) { return '0 - 1'; }
|
|
|
|
return `${arr[i - 1]} - ${arr[i]}`;
|
|
|
|
} else {
|
|
|
|
return `${this.limitFee}+`;
|
|
|
|
}
|
|
|
|
});
|
2021-08-21 06:46:28 +02:00
|
|
|
this.mountFeeChart();
|
2020-03-29 18:59:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ngOnChanges() {
|
2021-08-25 06:01:35 +02:00
|
|
|
this.windowPreference = this.storageService.getValue('graphWindowPreference');
|
2020-03-29 18:59:04 +02:00
|
|
|
this.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([]));
|
2021-08-21 06:46:28 +02:00
|
|
|
this.mountFeeChart();
|
2020-03-29 18:59:04 +02:00
|
|
|
}
|
|
|
|
|
2021-08-25 06:01:35 +02:00
|
|
|
onChartReady(myChart: any) {
|
|
|
|
myChart.on('mouseover', 'series', (serie: any) => {
|
|
|
|
this.hoverIndexSerie = serie.seriesIndex;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-03-29 18:59:04 +02:00
|
|
|
handleNewMempoolData(mempoolStats: OptimizedMempoolStats[]) {
|
|
|
|
mempoolStats.reverse();
|
|
|
|
const labels = mempoolStats.map(stats => stats.added);
|
2021-08-21 06:46:28 +02:00
|
|
|
const finalArrayVByte = this.generateArray(mempoolStats);
|
2020-03-29 18:59:04 +02:00
|
|
|
|
|
|
|
// Only Liquid has lower than 1 sat/vb transactions
|
2020-05-17 12:06:09 +02:00
|
|
|
if (this.stateService.network !== 'liquid') {
|
2021-08-21 06:46:28 +02:00
|
|
|
finalArrayVByte.shift();
|
2020-03-29 18:59:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
labels: labels,
|
2021-08-21 06:46:28 +02:00
|
|
|
series: finalArrayVByte
|
2020-03-29 18:59:04 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
generateArray(mempoolStats: OptimizedMempoolStats[]) {
|
|
|
|
const finalArray: number[][] = [];
|
|
|
|
let feesArray: number[] = [];
|
|
|
|
for (let index = 37; index > -1; index--) {
|
|
|
|
feesArray = [];
|
|
|
|
mempoolStats.forEach((stats) => {
|
2021-09-07 22:48:43 +02:00
|
|
|
feesArray.push(stats.vsizes[index] ? stats.vsizes[index] : 0);
|
2020-03-29 18:59:04 +02:00
|
|
|
});
|
|
|
|
finalArray.push(feesArray);
|
|
|
|
}
|
|
|
|
finalArray.reverse();
|
|
|
|
return finalArray;
|
|
|
|
}
|
2021-08-21 06:46:28 +02:00
|
|
|
|
2021-09-13 16:07:17 +02:00
|
|
|
mountFeeChart() {
|
2021-08-21 06:46:28 +02:00
|
|
|
const { labels, series } = this.mempoolVsizeFeesData;
|
|
|
|
|
2021-09-07 22:48:43 +02:00
|
|
|
const seriesGraph = series.map((value: Array<number>, index: number) => {
|
2021-08-27 04:30:57 +02:00
|
|
|
if (index <= this.feeLimitIndex){
|
2021-08-25 06:01:35 +02:00
|
|
|
return {
|
2021-09-13 16:07:17 +02:00
|
|
|
name: this.feeLevelsOrdered[index],
|
2021-08-25 06:01:35 +02:00
|
|
|
type: 'line',
|
|
|
|
stack: 'total',
|
|
|
|
smooth: false,
|
|
|
|
markPoint: {
|
|
|
|
symbol: 'rect',
|
2021-08-21 06:46:28 +02:00
|
|
|
},
|
|
|
|
lineStyle: {
|
2021-08-25 06:01:35 +02:00
|
|
|
width: 0,
|
|
|
|
opacity: 0,
|
2021-08-21 06:46:28 +02:00
|
|
|
},
|
2021-09-13 16:07:17 +02:00
|
|
|
symbolSize: (this.template === 'advanced') ? 10 : 10,
|
2021-08-25 06:01:35 +02:00
|
|
|
showSymbol: false,
|
|
|
|
areaStyle: {
|
|
|
|
opacity: 1,
|
|
|
|
color: chartColors[index],
|
|
|
|
},
|
|
|
|
emphasis: {
|
|
|
|
focus: 'series',
|
2021-09-13 16:07:17 +02:00
|
|
|
areaStyle: {
|
|
|
|
opacity: 1,
|
|
|
|
},
|
|
|
|
itemStyle: {
|
|
|
|
opacity: 0.2,
|
2021-08-27 04:30:57 +02:00
|
|
|
},
|
2021-08-25 06:01:35 +02:00
|
|
|
},
|
|
|
|
itemStyle: {
|
2021-09-13 16:07:17 +02:00
|
|
|
opacity: 0,
|
2021-08-25 06:01:35 +02:00
|
|
|
},
|
|
|
|
data: this.vbytesPipe.transform(value, 2, 'vB', 'MvB', true)
|
|
|
|
};
|
|
|
|
}
|
2021-08-21 06:46:28 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
this.mempoolVsizeFeesOptions = {
|
2021-09-13 16:07:17 +02:00
|
|
|
emphasis: {
|
|
|
|
areaStyle: {
|
|
|
|
opacity: 1,
|
|
|
|
}
|
|
|
|
},
|
2021-09-15 03:35:34 +02:00
|
|
|
color: chartColors,
|
2021-08-21 06:46:28 +02:00
|
|
|
tooltip: {
|
2021-09-16 22:30:13 +02:00
|
|
|
show: (window.innerWidth >= 768) ? true : false,
|
2021-08-21 06:46:28 +02:00
|
|
|
trigger: 'axis',
|
2021-09-13 16:07:17 +02:00
|
|
|
alwaysShowContent: false,
|
2021-08-21 06:46:28 +02:00
|
|
|
position: (pos, params, el, elRect, size) => {
|
2021-08-27 04:30:57 +02:00
|
|
|
const positions = { top: (this.template === 'advanced') ? 30 : -30 };
|
|
|
|
positions[['left', 'right'][+(pos[0] < size.viewSize[0] / 2)]] = 60;
|
2021-08-21 06:46:28 +02:00
|
|
|
return positions;
|
|
|
|
},
|
2021-09-13 16:07:17 +02:00
|
|
|
extraCssText: `width: ${(this.template === 'advanced') ? '275px' : '200px'};
|
2021-08-21 06:46:28 +02:00
|
|
|
background: transparent;
|
|
|
|
border: none;
|
|
|
|
box-shadow: none;`,
|
|
|
|
axisPointer: {
|
2021-08-25 06:01:35 +02:00
|
|
|
type: 'line',
|
2021-08-21 06:46:28 +02:00
|
|
|
},
|
|
|
|
formatter: (params: any) => {
|
2021-09-13 16:07:17 +02:00
|
|
|
const legendName = (index: number) => this.feeLevelsOrdered[index];
|
2021-09-15 03:35:34 +02:00
|
|
|
const colorSpan = (index: number) => `<span class="indicator" style="background-color: ${chartColors[index]}"></span>
|
|
|
|
<span>
|
|
|
|
${legendName(index)}
|
|
|
|
</span>`;
|
2021-09-13 16:07:17 +02:00
|
|
|
const totals = (values: any) => {
|
2021-09-13 16:19:29 +02:00
|
|
|
let totalValueTemp = 0;
|
|
|
|
const totalValueArrayTemp = [];
|
2021-09-13 16:07:17 +02:00
|
|
|
const valuesInverted = values.slice(0).reverse();
|
|
|
|
for (const item of valuesInverted) {
|
2021-09-13 16:19:29 +02:00
|
|
|
totalValueTemp += item.value;
|
|
|
|
totalValueArrayTemp.push(totalValueTemp);
|
2021-09-13 16:07:17 +02:00
|
|
|
}
|
2021-09-13 16:19:29 +02:00
|
|
|
return {
|
|
|
|
totalValue: totalValueTemp,
|
|
|
|
totalValueArray: totalValueArrayTemp.reverse()
|
|
|
|
};
|
2021-09-13 16:07:17 +02:00
|
|
|
};
|
|
|
|
const { totalValue, totalValueArray } = totals(params);
|
2021-09-15 03:35:34 +02:00
|
|
|
const title = `<div class="title">
|
|
|
|
${params[0].axisValue}
|
|
|
|
<span class="total-value">
|
|
|
|
${this.vbytesPipe.transform(totalValue, 2, 'vB', 'MvB', false)}
|
|
|
|
</span>
|
|
|
|
</div>`;
|
2021-08-27 04:30:57 +02:00
|
|
|
const itemFormatted = [];
|
|
|
|
let totalParcial = 0;
|
|
|
|
let progressPercentageText = '';
|
2021-09-07 22:48:43 +02:00
|
|
|
params.map((item: any, index: number) => {
|
2021-08-27 04:30:57 +02:00
|
|
|
totalParcial += item.value;
|
|
|
|
let progressPercentage = 0;
|
2021-09-13 16:07:17 +02:00
|
|
|
let progressPercentageSum = 0;
|
2021-08-27 04:30:57 +02:00
|
|
|
if (index <= this.feeLimitIndex) {
|
2021-09-13 16:07:17 +02:00
|
|
|
progressPercentage = (item.value / totalValue) * 100;
|
|
|
|
progressPercentageSum = (totalValueArray[index] / totalValue) * 100;
|
2021-08-25 06:01:35 +02:00
|
|
|
let activeItemClass = '';
|
2021-08-27 04:30:57 +02:00
|
|
|
if (this.hoverIndexSerie === index) {
|
|
|
|
progressPercentageText = `<div class="total-parcial-active">
|
2021-09-13 16:19:29 +02:00
|
|
|
<span class="progress-percentage">
|
|
|
|
${progressPercentage.toFixed(2)}
|
|
|
|
<span class="symbol">%</span>
|
|
|
|
</span>
|
2021-09-15 03:35:34 +02:00
|
|
|
<span class="total-parcial-vbytes">
|
|
|
|
${this.vbytesPipe.transform(totalParcial, 2, 'vB', 'MvB', false)}
|
|
|
|
</span>
|
2021-09-13 16:19:29 +02:00
|
|
|
<div class="total-percentage-bar">
|
|
|
|
<span>
|
|
|
|
<span style="width: ${progressPercentage}%; background: ${chartColors[index]}"></span>
|
|
|
|
</span>
|
|
|
|
</div>
|
2021-08-27 04:30:57 +02:00
|
|
|
</div>`;
|
2021-08-25 06:01:35 +02:00
|
|
|
activeItemClass = 'active';
|
|
|
|
}
|
2021-08-27 04:30:57 +02:00
|
|
|
itemFormatted.push(`<tr class="item ${activeItemClass}">
|
2021-09-15 03:35:34 +02:00
|
|
|
<td class="indicator-container">
|
|
|
|
${colorSpan(index)}
|
|
|
|
</td>
|
|
|
|
<td class="value">
|
|
|
|
${this.vbytesPipe.transform(item.value, 2, 'vB', 'MvB', false)}
|
|
|
|
</td>
|
2021-09-13 16:07:17 +02:00
|
|
|
<td class="total-progress-sum">
|
2021-09-15 03:35:34 +02:00
|
|
|
<span>
|
|
|
|
${this.vbytesPipe.transform(totalValueArray[index], 2, 'vB', 'MvB', false)}
|
|
|
|
</span>
|
2021-09-13 16:07:17 +02:00
|
|
|
</td>
|
|
|
|
<td class="total-progress-sum-bar">
|
|
|
|
<div>
|
|
|
|
<span style="width: ${progressPercentageSum.toFixed(2)}%; background-color: ${chartColors[index]}"></span>
|
|
|
|
</div>
|
|
|
|
</td>
|
2021-08-27 04:30:57 +02:00
|
|
|
</tr>`);
|
2021-08-21 06:46:28 +02:00
|
|
|
}
|
|
|
|
});
|
2021-09-15 03:35:34 +02:00
|
|
|
const classActive = (this.template === 'advanced') ? 'fees-wrapper-tooltip-chart-advanced' : '';
|
|
|
|
return `<div class="fees-wrapper-tooltip-chart ${classActive}">
|
2021-08-27 04:30:57 +02:00
|
|
|
${title}
|
|
|
|
<table>
|
|
|
|
<thead>
|
|
|
|
<tr>
|
|
|
|
<th>Range</th>
|
|
|
|
<th>Size</th>
|
2021-09-13 16:07:17 +02:00
|
|
|
<th>Sum</th>
|
|
|
|
<th></th>
|
2021-08-27 04:30:57 +02:00
|
|
|
</tr>
|
|
|
|
</thead>
|
2021-09-15 03:35:34 +02:00
|
|
|
<tbody>
|
|
|
|
${itemFormatted.reverse().join('')}
|
|
|
|
</tbody>
|
2021-08-27 04:30:57 +02:00
|
|
|
</table>
|
2021-09-15 03:35:34 +02:00
|
|
|
<span class="total-value">
|
|
|
|
${progressPercentageText}
|
|
|
|
</span>
|
2021-08-25 06:01:35 +02:00
|
|
|
</div>`;
|
2021-08-21 06:46:28 +02:00
|
|
|
}
|
|
|
|
},
|
2021-08-25 06:01:35 +02:00
|
|
|
dataZoom: [{
|
|
|
|
type: 'inside',
|
|
|
|
realtime: true,
|
2021-08-27 04:30:57 +02:00
|
|
|
zoomOnMouseWheel: (this.template === 'advanced') ? true : false,
|
|
|
|
maxSpan: 100,
|
|
|
|
minSpan: 10,
|
2021-08-25 06:01:35 +02:00
|
|
|
}, {
|
2021-08-27 04:30:57 +02:00
|
|
|
show: (this.template === 'advanced' && this.showZoom) ? true : false,
|
2021-08-25 06:01:35 +02:00
|
|
|
type: 'slider',
|
|
|
|
brushSelect: false,
|
|
|
|
realtime: true,
|
2021-09-13 16:07:17 +02:00
|
|
|
bottom: 0,
|
2021-08-25 06:01:35 +02:00
|
|
|
selectedDataBackground: {
|
|
|
|
lineStyle: {
|
|
|
|
color: '#fff',
|
|
|
|
opacity: 0.45,
|
|
|
|
},
|
|
|
|
areaStyle: {
|
|
|
|
opacity: 0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}],
|
2021-08-27 04:30:57 +02:00
|
|
|
animation: false,
|
2021-08-21 06:46:28 +02:00
|
|
|
grid: {
|
|
|
|
height: this.height,
|
|
|
|
right: this.right,
|
|
|
|
top: this.top,
|
|
|
|
left: this.left,
|
|
|
|
},
|
|
|
|
xAxis: [
|
|
|
|
{
|
|
|
|
type: 'category',
|
|
|
|
boundaryGap: false,
|
2021-09-17 14:40:51 +02:00
|
|
|
axisLine: { onZero: true },
|
|
|
|
axisLabel: {
|
|
|
|
align: 'center',
|
|
|
|
fontSize: 11,
|
|
|
|
lineHeight: 12,
|
|
|
|
},
|
2021-09-13 16:07:17 +02:00
|
|
|
data: labels.map((value: any) => `${formatDate(value, 'M/d', this.locale)}\n${formatDate(value, 'H:mm', this.locale)}`),
|
2021-08-21 06:46:28 +02:00
|
|
|
}
|
|
|
|
],
|
|
|
|
yAxis: {
|
|
|
|
type: 'value',
|
2021-08-25 06:01:35 +02:00
|
|
|
axisLine: { onZero: false },
|
2021-08-21 06:46:28 +02:00
|
|
|
axisLabel: {
|
2021-09-17 14:40:51 +02:00
|
|
|
fontSize: 11,
|
2021-08-21 06:46:28 +02:00
|
|
|
formatter: (value: number) => (`${this.vbytesPipe.transform(value, 2, 'vB', 'MvB', true)}`),
|
|
|
|
},
|
|
|
|
splitLine: {
|
|
|
|
lineStyle: {
|
|
|
|
type: 'dotted',
|
|
|
|
color: '#ffffff66',
|
|
|
|
opacity: 0.25,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2021-09-07 22:48:43 +02:00
|
|
|
series: seriesGraph
|
2021-08-21 06:46:28 +02:00
|
|
|
};
|
|
|
|
}
|
2020-03-29 18:59:04 +02:00
|
|
|
}
|
2021-09-13 16:07:17 +02:00
|
|
|
|