Merge pull request #1004 from jorisvial/bug/improve-x-axis-intervals

Improve x-axis labels and graph data ticks
This commit is contained in:
wiz 2021-12-14 07:50:14 +00:00 committed by GitHub
commit e67f552fbd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 188 additions and 143 deletions

View File

@ -267,8 +267,57 @@ class Statistics {
} }
} }
private getQueryForDays(div: number) { private getQueryForDaysAvg(div: number, interval: string) {
return `SELECT id, added, unconfirmed_transactions, 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, tx_per_second,
vbytes_per_second, vbytes_per_second,
vsize_1, vsize_1,
@ -308,13 +357,17 @@ class Statistics {
vsize_1400, vsize_1400,
vsize_1600, vsize_1600,
vsize_1800, 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> { public async $get(id: number): Promise<OptimizedStatistic | undefined> {
try { try {
const connection = await DB.pool.getConnection(); 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]); const [rows] = await connection.query<any>(query, [id]);
connection.release(); connection.release();
if (rows[0]) { if (rows[0]) {
@ -328,7 +381,7 @@ class Statistics {
public async $list2H(): Promise<OptimizedStatistic[]> { public async $list2H(): Promise<OptimizedStatistic[]> {
try { try {
const connection = await DB.pool.getConnection(); 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 }); const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release(); connection.release();
return this.mapStatisticToOptimizedStatistic(rows); return this.mapStatisticToOptimizedStatistic(rows);
@ -341,7 +394,7 @@ class Statistics {
public async $list24H(): Promise<OptimizedStatistic[]> { public async $list24H(): Promise<OptimizedStatistic[]> {
try { try {
const connection = await DB.pool.getConnection(); 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 }); const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release(); connection.release();
return this.mapStatisticToOptimizedStatistic(rows); return this.mapStatisticToOptimizedStatistic(rows);
@ -354,7 +407,7 @@ class Statistics {
public async $list1W(): Promise<OptimizedStatistic[]> { public async $list1W(): Promise<OptimizedStatistic[]> {
try { try {
const connection = await DB.pool.getConnection(); 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 }); const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release(); connection.release();
return this.mapStatisticToOptimizedStatistic(rows); return this.mapStatisticToOptimizedStatistic(rows);
@ -367,7 +420,7 @@ class Statistics {
public async $list1M(): Promise<OptimizedStatistic[]> { public async $list1M(): Promise<OptimizedStatistic[]> {
try { try {
const connection = await DB.pool.getConnection(); 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 }); const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release(); connection.release();
return this.mapStatisticToOptimizedStatistic(rows); return this.mapStatisticToOptimizedStatistic(rows);
@ -380,7 +433,7 @@ class Statistics {
public async $list3M(): Promise<OptimizedStatistic[]> { public async $list3M(): Promise<OptimizedStatistic[]> {
try { try {
const connection = await DB.pool.getConnection(); 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 }); const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release(); connection.release();
return this.mapStatisticToOptimizedStatistic(rows); return this.mapStatisticToOptimizedStatistic(rows);
@ -393,7 +446,7 @@ class Statistics {
public async $list6M(): Promise<OptimizedStatistic[]> { public async $list6M(): Promise<OptimizedStatistic[]> {
try { try {
const connection = await DB.pool.getConnection(); 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 }); const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release(); connection.release();
return this.mapStatisticToOptimizedStatistic(rows); return this.mapStatisticToOptimizedStatistic(rows);
@ -406,7 +459,7 @@ class Statistics {
public async $list1Y(): Promise<OptimizedStatistic[]> { public async $list1Y(): Promise<OptimizedStatistic[]> {
try { try {
const connection = await DB.pool.getConnection(); 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 }); const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release(); connection.release();
return this.mapStatisticToOptimizedStatistic(rows); return this.mapStatisticToOptimizedStatistic(rows);
@ -419,7 +472,7 @@ class Statistics {
public async $list2Y(): Promise<OptimizedStatistic[]> { public async $list2Y(): Promise<OptimizedStatistic[]> {
try { try {
const connection = await DB.pool.getConnection(); 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 }); const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release(); connection.release();
return this.mapStatisticToOptimizedStatistic(rows); return this.mapStatisticToOptimizedStatistic(rows);
@ -432,7 +485,7 @@ class Statistics {
public async $list3Y(): Promise<OptimizedStatistic[]> { public async $list3Y(): Promise<OptimizedStatistic[]> {
try { try {
const connection = await DB.pool.getConnection(); 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 }); const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release(); connection.release();
return this.mapStatisticToOptimizedStatistic(rows); return this.mapStatisticToOptimizedStatistic(rows);

View File

@ -32,7 +32,6 @@ import { BlockchainComponent } from './components/blockchain/blockchain.componen
import { FooterComponent } from './components/footer/footer.component'; import { FooterComponent } from './components/footer/footer.component';
import { AudioService } from './services/audio.service'; import { AudioService } from './services/audio.service';
import { MempoolBlockComponent } from './components/mempool-block/mempool-block.component'; 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 { IncomingTransactionsGraphComponent } from './components/incoming-transactions-graph/incoming-transactions-graph.component';
import { TimeSpanComponent } from './components/time-span/time-span.component'; import { TimeSpanComponent } from './components/time-span/time-span.component';
import { SeoService } from './services/seo.service'; import { SeoService } from './services/seo.service';
@ -84,7 +83,6 @@ import { PushTransactionComponent } from './components/push-transaction/push-tra
MempoolBlocksComponent, MempoolBlocksComponent,
FooterComponent, FooterComponent,
MempoolBlockComponent, MempoolBlockComponent,
FeeDistributionGraphComponent,
IncomingTransactionsGraphComponent, IncomingTransactionsGraphComponent,
MempoolGraphComponent, MempoolGraphComponent,
LbtcPegsGraphComponent, LbtcPegsGraphComponent,

View File

@ -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>

View File

@ -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,
}
}]
};
}
}

View File

@ -1,8 +1,8 @@
import { Component, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnInit } from '@angular/core'; import { Component, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { formatDate } from '@angular/common';
import { EChartsOption } from 'echarts'; import { EChartsOption } from 'echarts';
import { OnChanges } from '@angular/core'; import { OnChanges } from '@angular/core';
import { StorageService } from 'src/app/services/storage.service'; import { StorageService } from 'src/app/services/storage.service';
import { formatterXAxis, formatterXAxisLabel } from 'src/app/shared/graphs.utils';
@Component({ @Component({
selector: 'app-incoming-transactions-graph', selector: 'app-incoming-transactions-graph',
@ -25,6 +25,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
@Input() top: number | string = '20'; @Input() top: number | string = '20';
@Input() left: number | string = '0'; @Input() left: number | string = '0';
@Input() template: ('widget' | 'advanced') = 'widget'; @Input() template: ('widget' | 'advanced') = 'widget';
@Input() windowPreferenceOverride: string;
isLoading = true; isLoading = true;
mempoolStatsChartOption: EChartsOption = {}; mempoolStatsChartOption: EChartsOption = {};
@ -46,7 +47,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
if (!this.data) { if (!this.data) {
return; return;
} }
this.windowPreference = this.storageService.getValue('graphWindowPreference'); this.windowPreference = this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference');
this.mountChart(); this.mountChart();
} }
@ -73,10 +74,12 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
maxSpan: 100, maxSpan: 100,
minSpan: 10, minSpan: 10,
}, { }, {
showDetail: false,
show: (this.template === 'advanced') ? true : false, show: (this.template === 'advanced') ? true : false,
type: 'slider', type: 'slider',
brushSelect: false, brushSelect: false,
realtime: true, realtime: true,
bottom: 0,
selectedDataBackground: { selectedDataBackground: {
lineStyle: { lineStyle: {
color: '#fff', color: '#fff',
@ -85,7 +88,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
areaStyle: { areaStyle: {
opacity: 0, opacity: 0,
} }
} },
}], }],
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
@ -102,29 +105,39 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
type: 'line', type: 'line',
}, },
formatter: (params: any) => { 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>`; 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) => { params.map((item: any, index: number) => {
if (index < 26) { if (index < 26) {
itemFormatted += `<div class="item"> itemFormatted += `<div class="item">
<div class="indicator-container">${colorSpan(item.color)}</div> <div class="indicator-container">${colorSpan(item.color)}</div>
<div class="grow"></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>`; </div>`;
} }
}); });
return `<div class="tx-wrapper-tooltip-chart ${(this.template === 'advanced') ? 'tx-wrapper-tooltip-chart-advanced' : ''}">${itemFormatted}</div>`; return `<div class="tx-wrapper-tooltip-chart ${(this.template === 'advanced') ? 'tx-wrapper-tooltip-chart-advanced' : ''}">${itemFormatted}</div>`;
} }
}, },
xAxis: { xAxis: [
type: 'category', {
axisLabel: { name: formatterXAxisLabel(this.locale, this.windowPreference),
align: 'center', nameLocation: 'middle',
fontSize: 11, nameTextStyle: {
lineHeight: 12 padding: [20, 0, 0, 0],
}, },
data: this.data.labels.map((value: any) => `${formatDate(value, 'M/d', this.locale)}\n${formatDate(value, 'H:mm', this.locale)}`), type: 'time',
}, axisLabel: {
margin: 20,
align: 'center',
fontSize: 11,
lineHeight: 12,
hideOverlap: true,
padding: [0, 5],
},
}
],
yAxis: { yAxis: {
type: 'value', type: 'value',
axisLabel: { axisLabel: {

View File

@ -40,9 +40,6 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="col-md chart-container">
<app-fee-distribution-graph [data]="mempoolBlock.feeRange" ></app-fee-distribution-graph>
</div>
</div> </div>
</div> </div>

View File

@ -1,13 +1,12 @@
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 { formatDate } from '@angular/common';
import { VbytesPipe } from 'src/app/shared/pipes/bytes-pipe/vbytes.pipe'; import { VbytesPipe } from 'src/app/shared/pipes/bytes-pipe/vbytes.pipe';
import { formatNumber } from "@angular/common"; import { formatNumber } from "@angular/common";
import { OptimizedMempoolStats } from 'src/app/interfaces/node-api.interface'; import { OptimizedMempoolStats } from 'src/app/interfaces/node-api.interface';
import { StateService } from 'src/app/services/state.service'; import { StateService } from 'src/app/services/state.service';
import { StorageService } from 'src/app/services/storage.service'; import { StorageService } from 'src/app/services/storage.service';
import { EChartsOption } from 'echarts'; import { EChartsOption } from 'echarts';
import { feeLevels, chartColors } from 'src/app/app.constants'; import { feeLevels, chartColors } from 'src/app/app.constants';
import { formatterXAxis, formatterXAxisLabel } from 'src/app/shared/graphs.utils';
@Component({ @Component({
selector: 'app-mempool-graph', selector: 'app-mempool-graph',
@ -32,6 +31,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
@Input() left: number | string = 75; @Input() left: number | string = 75;
@Input() template: ('widget' | 'advanced') = 'widget'; @Input() template: ('widget' | 'advanced') = 'widget';
@Input() showZoom = true; @Input() showZoom = true;
@Input() windowPreferenceOverride: string;
isLoading = true; isLoading = true;
mempoolVsizeFeesData: any; mempoolVsizeFeesData: any;
@ -62,7 +62,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
if (!this.data) { if (!this.data) {
return; 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.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([]));
this.mountFeeChart(); this.mountFeeChart();
} }
@ -97,13 +97,13 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
} }
generateArray(mempoolStats: OptimizedMempoolStats[]) { generateArray(mempoolStats: OptimizedMempoolStats[]) {
const finalArray: number[][] = []; const finalArray: number[][][] = [];
let feesArray: number[] = []; let feesArray: number[][] = [];
let limitFeesTemplate = this.template === 'advanced' ? 26 : 20; let limitFeesTemplate = this.template === 'advanced' ? 26 : 20;
for (let index = limitFeesTemplate; index > -1; index--) { for (let index = limitFeesTemplate; index > -1; index--) {
feesArray = []; feesArray = [];
mempoolStats.forEach((stats) => { 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); finalArray.push(feesArray);
} }
@ -113,7 +113,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
mountFeeChart() { mountFeeChart() {
this.orderLevels(); this.orderLevels();
const { labels, series } = this.mempoolVsizeFeesData; const { series } = this.mempoolVsizeFeesData;
const seriesGraph = []; const seriesGraph = [];
const newColors = []; const newColors = [];
@ -186,14 +186,15 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
type: 'line', type: 'line',
}, },
formatter: (params: any) => { formatter: (params: any) => {
const axisValueLabel: string = formatterXAxis(this.locale, this.windowPreference, params[0].axisValue);
const { totalValue, totalValueArray } = this.getTotalValues(params); const { totalValue, totalValueArray } = this.getTotalValues(params);
const itemFormatted = []; const itemFormatted = [];
let totalParcial = 0; let totalParcial = 0;
let progressPercentageText = ''; let progressPercentageText = '';
const items = this.inverted ? [...params].reverse() : params; const items = this.inverted ? [...params].reverse() : params;
items.map((item: any, index: number) => { items.map((item: any, index: number) => {
totalParcial += item.value; totalParcial += item.value[1];
const progressPercentage = (item.value / totalValue) * 100; const progressPercentage = (item.value[1] / totalValue) * 100;
const progressPercentageSum = (totalValueArray[index] / totalValue) * 100; const progressPercentageSum = (totalValueArray[index] / totalValue) * 100;
let activeItemClass = ''; let activeItemClass = '';
let hoverActive = 0; let hoverActive = 0;
@ -233,7 +234,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
</td> </td>
<td class="total-progress-sum"> <td class="total-progress-sum">
<span> <span>
${this.vbytesPipe.transform(item.value, 2, 'vB', 'MvB', false)} ${this.vbytesPipe.transform(item.value[1], 2, 'vB', 'MvB', false)}
</span> </span>
</td> </td>
<td class="total-progress-sum"> <td class="total-progress-sum">
@ -257,7 +258,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
const titleSum = $localize`Sum`; const titleSum = $localize`Sum`;
return `<div class="fees-wrapper-tooltip-chart ${classActive}"> return `<div class="fees-wrapper-tooltip-chart ${classActive}">
<div class="title"> <div class="title">
${params[0].axisValue} ${axisValueLabel}
<span class="total-value"> <span class="total-value">
${this.vbytesPipe.transform(totalValue, 2, 'vB', 'MvB', false)} ${this.vbytesPipe.transform(totalValue, 2, 'vB', 'MvB', false)}
</span> </span>
@ -288,6 +289,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
maxSpan: 100, maxSpan: 100,
minSpan: 10, minSpan: 10,
}, { }, {
showDetail: false,
show: (this.template === 'advanced' && this.showZoom) ? true : false, show: (this.template === 'advanced' && this.showZoom) ? true : false,
type: 'slider', type: 'slider',
brushSelect: false, brushSelect: false,
@ -312,15 +314,22 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
}, },
xAxis: [ xAxis: [
{ {
type: 'category', name: formatterXAxisLabel(this.locale, this.windowPreference),
nameLocation: 'middle',
nameTextStyle: {
padding: [20, 0, 0, 0],
},
type: 'time',
boundaryGap: false, boundaryGap: false,
axisLine: { onZero: true }, axisLine: { onZero: true },
axisLabel: { axisLabel: {
margin: 20,
align: 'center', align: 'center',
fontSize: 11, fontSize: 11,
lineHeight: 12, 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: { yAxis: {
@ -346,7 +355,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
const totalValueArray = []; const totalValueArray = [];
const valuesInverted = this.inverted ? values : [...values].reverse(); const valuesInverted = this.inverted ? values : [...values].reverse();
for (const item of valuesInverted) { for (const item of valuesInverted) {
totalValueTemp += item.value; totalValueTemp += item.value[1];
totalValueArray.push(totalValueTemp); totalValueArray.push(totalValueTemp);
} }
return { return {

View File

@ -134,7 +134,7 @@ export class StatisticsComponent implements OnInit {
this.mempoolTransactionsWeightPerSecondData = { this.mempoolTransactionsWeightPerSecondData = {
labels: labels, labels: labels,
series: [mempoolStats.map((stats) => stats.vbytes_per_second)], series: [mempoolStats.map((stats) => [stats.added * 1000, stats.vbytes_per_second])],
}; };
} }

View File

@ -54,6 +54,7 @@
[limitFee]="150" [limitFee]="150"
[limitFilterFee]="1" [limitFilterFee]="1"
[data]="mempoolStats.value?.mempool" [data]="mempoolStats.value?.mempool"
[windowPreferenceOverride]="'2h'"
></app-mempool-graph> ></app-mempool-graph>
</div> </div>
</ng-container> </ng-container>
@ -73,6 +74,7 @@
<app-incoming-transactions-graph <app-incoming-transactions-graph
[left]="50" [left]="50"
[data]="mempoolStats.value?.weightPerSecond" [data]="mempoolStats.value?.weightPerSecond"
[windowPreferenceOverride]="'2h'"
></app-incoming-transactions-graph> ></app-incoming-transactions-graph>
</div> </div>
</ng-template> </ng-template>

View File

@ -254,7 +254,6 @@ export class DashboardComponent implements OnInit {
); );
}), }),
map((mempoolStats) => { map((mempoolStats) => {
const data = this.handleNewMempoolData(mempoolStats.concat([]));
return { return {
mempool: mempoolStats, mempool: mempoolStats,
weightPerSecond: this.handleNewMempoolData(mempoolStats.concat([])), weightPerSecond: this.handleNewMempoolData(mempoolStats.concat([])),
@ -286,7 +285,7 @@ export class DashboardComponent implements OnInit {
return { return {
labels: labels, labels: labels,
series: [mempoolStats.map((stats) => stats.vbytes_per_second)], series: [mempoolStats.map((stats) => [stats.added * 1000, stats.vbytes_per_second])],
}; };
} }

View File

@ -1,6 +1,6 @@
export interface OptimizedMempoolStats { export interface OptimizedMempoolStats {
id: number; id: number;
added: string; added: number;
unconfirmed_transactions: number; unconfirmed_transactions: number;
tx_per_second: number; tx_per_second: number;
vbytes_per_second: number; vbytes_per_second: number;

View File

@ -1,9 +1,25 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class StorageService { 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 { getValue(key: string): string {
try { try {
return localStorage.getItem(key); return localStorage.getItem(key);

View 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;
}
};

View File

@ -555,7 +555,7 @@ html:lang(ru) .card-title {
} }
.tx-wrapper-tooltip-chart-advanced { .tx-wrapper-tooltip-chart-advanced {
width: 115px; width: 140px;
.indicator-container { .indicator-container {
.indicator { .indicator {
margin-right: 5px; margin-right: 5px;