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) {
|
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);
|
||||||
|
@ -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,
|
||||||
|
@ -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 { 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: {
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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])],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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])],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
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 {
|
.tx-wrapper-tooltip-chart-advanced {
|
||||||
width: 115px;
|
width: 140px;
|
||||||
.indicator-container {
|
.indicator-container {
|
||||||
.indicator {
|
.indicator {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user