mirror of
https://github.com/mempool/mempool.git
synced 2025-01-19 05:34:03 +01:00
Add new component incoming-transactions-graph;
Refactor component mempool-graph; Refactor component fee-distribution-graph; Add incoming-transactions-graph to dashboard; Add incoming-transactions-graph to statistics; Add incoming-transactions-graph to television; Add mempool-graph to dashboard; Add mempool-graph to statistics; Add mempool-graph to television; Remove chartist.component;
This commit is contained in:
parent
1a98a14541
commit
9b956ff88d
14
frontend/package-lock.json
generated
14
frontend/package-lock.json
generated
@ -24,7 +24,6 @@
|
|||||||
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
||||||
"@juggle/resize-observer": "^3.3.1",
|
"@juggle/resize-observer": "^3.3.1",
|
||||||
"@mempool/chartist": "^0.11.4",
|
|
||||||
"@mempool/mempool.js": "^2.2.4",
|
"@mempool/mempool.js": "^2.2.4",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^7.0.0",
|
"@ng-bootstrap/ng-bootstrap": "^7.0.0",
|
||||||
"@nguniversal/express-engine": "11.2.1",
|
"@nguniversal/express-engine": "11.2.1",
|
||||||
@ -2260,14 +2259,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.3.1.tgz",
|
||||||
"integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw=="
|
"integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw=="
|
||||||
},
|
},
|
||||||
"node_modules/@mempool/chartist": {
|
|
||||||
"version": "0.11.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@mempool/chartist/-/chartist-0.11.4.tgz",
|
|
||||||
"integrity": "sha512-wSemsw2NIWS7/SHxjDe9upSdUETxNRebY0ByaJzcONKUzJSUzMuSNmKEdD3kr/g02H++JvsXR2znLC6tYEAbPA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.6.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@mempool/mempool.js": {
|
"node_modules/@mempool/mempool.js": {
|
||||||
"version": "2.2.4",
|
"version": "2.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@mempool/mempool.js/-/mempool.js-2.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@mempool/mempool.js/-/mempool.js-2.2.4.tgz",
|
||||||
@ -21966,11 +21957,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.3.1.tgz",
|
||||||
"integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw=="
|
"integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw=="
|
||||||
},
|
},
|
||||||
"@mempool/chartist": {
|
|
||||||
"version": "0.11.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@mempool/chartist/-/chartist-0.11.4.tgz",
|
|
||||||
"integrity": "sha512-wSemsw2NIWS7/SHxjDe9upSdUETxNRebY0ByaJzcONKUzJSUzMuSNmKEdD3kr/g02H++JvsXR2znLC6tYEAbPA=="
|
|
||||||
},
|
|
||||||
"@mempool/mempool.js": {
|
"@mempool/mempool.js": {
|
||||||
"version": "2.2.4",
|
"version": "2.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@mempool/mempool.js/-/mempool.js-2.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@mempool/mempool.js/-/mempool.js-2.2.4.tgz",
|
||||||
|
@ -68,7 +68,6 @@
|
|||||||
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
||||||
"@juggle/resize-observer": "^3.3.1",
|
"@juggle/resize-observer": "^3.3.1",
|
||||||
"@mempool/chartist": "^0.11.4",
|
|
||||||
"@mempool/mempool.js": "^2.2.4",
|
"@mempool/mempool.js": "^2.2.4",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^7.0.0",
|
"@ng-bootstrap/ng-bootstrap": "^7.0.0",
|
||||||
"@nguniversal/express-engine": "11.2.1",
|
"@nguniversal/express-engine": "11.2.1",
|
||||||
|
@ -31,6 +31,46 @@ export const mempoolFeeColors = [
|
|||||||
'b9254b',
|
'b9254b',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const chartColors = [
|
||||||
|
"#D81B60",
|
||||||
|
"#8E24AA",
|
||||||
|
"#5E35B1",
|
||||||
|
"#3949AB",
|
||||||
|
"#1E88E5",
|
||||||
|
"#039BE5",
|
||||||
|
"#00ACC1",
|
||||||
|
"#00897B",
|
||||||
|
"#43A047",
|
||||||
|
"#7CB342",
|
||||||
|
"#C0CA33",
|
||||||
|
"#FDD835",
|
||||||
|
"#FFB300",
|
||||||
|
"#FB8C00",
|
||||||
|
"#F4511E",
|
||||||
|
"#6D4C41",
|
||||||
|
"#757575",
|
||||||
|
"#546E7A",
|
||||||
|
"#b71c1c",
|
||||||
|
"#880E4F",
|
||||||
|
"#4A148C",
|
||||||
|
"#311B92",
|
||||||
|
"#1A237E",
|
||||||
|
"#0D47A1",
|
||||||
|
"#01579B",
|
||||||
|
"#006064",
|
||||||
|
"#004D40",
|
||||||
|
"#1B5E20",
|
||||||
|
"#33691E",
|
||||||
|
"#827717",
|
||||||
|
"#F57F17",
|
||||||
|
"#FF6F00",
|
||||||
|
"#E65100",
|
||||||
|
"#BF360C",
|
||||||
|
"#3E2723",
|
||||||
|
"#212121",
|
||||||
|
"#263238",
|
||||||
|
];
|
||||||
|
|
||||||
export const feeLevels = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
|
export const feeLevels = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
|
||||||
250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000];
|
250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000];
|
||||||
|
|
||||||
|
@ -27,13 +27,13 @@ import { LiquidMasterPageComponent } from './components/liquid-master-page/liqui
|
|||||||
import { AboutComponent } from './components/about/about.component';
|
import { AboutComponent } from './components/about/about.component';
|
||||||
import { TelevisionComponent } from './components/television/television.component';
|
import { TelevisionComponent } from './components/television/television.component';
|
||||||
import { StatisticsComponent } from './components/statistics/statistics.component';
|
import { StatisticsComponent } from './components/statistics/statistics.component';
|
||||||
import { ChartistComponent } from './components/statistics/chartist.component';
|
|
||||||
import { BlockchainBlocksComponent } from './components/blockchain-blocks/blockchain-blocks.component';
|
import { BlockchainBlocksComponent } from './components/blockchain-blocks/blockchain-blocks.component';
|
||||||
import { BlockchainComponent } from './components/blockchain/blockchain.component';
|
import { BlockchainComponent } from './components/blockchain/blockchain.component';
|
||||||
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 { FeeDistributionGraphComponent } from './components/fee-distribution-graph/fee-distribution-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';
|
||||||
import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph.component';
|
import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph.component';
|
||||||
@ -79,10 +79,10 @@ import { SponsorComponent } from './components/sponsor/sponsor.component';
|
|||||||
TimeSpanComponent,
|
TimeSpanComponent,
|
||||||
AddressLabelsComponent,
|
AddressLabelsComponent,
|
||||||
MempoolBlocksComponent,
|
MempoolBlocksComponent,
|
||||||
ChartistComponent,
|
|
||||||
FooterComponent,
|
FooterComponent,
|
||||||
MempoolBlockComponent,
|
MempoolBlockComponent,
|
||||||
FeeDistributionGraphComponent,
|
FeeDistributionGraphComponent,
|
||||||
|
IncomingTransactionsGraphComponent,
|
||||||
MempoolGraphComponent,
|
MempoolGraphComponent,
|
||||||
AssetComponent,
|
AssetComponent,
|
||||||
AssetsComponent,
|
AssetsComponent,
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
<div style="height: 225px;" *ngIf="mempoolVsizeFeesData; else loadingFees">
|
<div class="fee-distribution-chart" *ngIf="mempoolVsizeFeesOptions; else loadingFees">
|
||||||
<app-chartist
|
<div echarts [options]="mempoolVsizeFeesOptions"></div>
|
||||||
[data]="mempoolVsizeFeesData"
|
|
||||||
[type]="'Line'"
|
|
||||||
[options]="mempoolVsizeFeesOptions">
|
|
||||||
</app-chartist>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #loadingFees>
|
<ng-template #loadingFees>
|
||||||
|
@ -1,70 +1,80 @@
|
|||||||
import { Component, Input, OnChanges, ChangeDetectionStrategy } from '@angular/core';
|
import { OnChanges } from '@angular/core';
|
||||||
import * as Chartist from '@mempool/chartist';
|
import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-fee-distribution-graph',
|
selector: 'app-fee-distribution-graph',
|
||||||
templateUrl: './fee-distribution-graph.component.html',
|
templateUrl: './fee-distribution-graph.component.html',
|
||||||
styleUrls: ['./fee-distribution-graph.component.scss'],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class FeeDistributionGraphComponent implements OnChanges {
|
export class FeeDistributionGraphComponent implements OnInit, OnChanges {
|
||||||
@Input() feeRange;
|
@Input() data: any;
|
||||||
|
@Input() height: number | string = 210;
|
||||||
|
@Input() top: number | string = 20;
|
||||||
|
@Input() right: number | string = 22;
|
||||||
|
@Input() left: number | string = 30;
|
||||||
|
|
||||||
mempoolVsizeFeesData: any;
|
|
||||||
mempoolVsizeFeesOptions: any;
|
mempoolVsizeFeesOptions: any;
|
||||||
|
|
||||||
feeLevels = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
|
constructor() { }
|
||||||
250, 300, 350, 400, 500];
|
|
||||||
|
|
||||||
constructor(
|
ngOnInit() {
|
||||||
) { }
|
this.mountChart();
|
||||||
|
|
||||||
ngOnChanges() {
|
|
||||||
this.mempoolVsizeFeesOptions = {
|
|
||||||
showArea: true,
|
|
||||||
showLine: true,
|
|
||||||
fullWidth: true,
|
|
||||||
showPoint: true,
|
|
||||||
low: 0,
|
|
||||||
axisY: {
|
|
||||||
showLabel: false,
|
|
||||||
offset: 0
|
|
||||||
},
|
|
||||||
axisX: {
|
|
||||||
showGrid: true,
|
|
||||||
showLabel: false,
|
|
||||||
offset: 0
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
Chartist.plugins.ctPointLabels({
|
|
||||||
textAnchor: 'middle',
|
|
||||||
labelInterpolationFnc: (value) => Math.round(value)
|
|
||||||
})
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
const fees = this.feeRange;
|
|
||||||
const series = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < this.feeLevels.length; i++) {
|
|
||||||
let total = 0;
|
|
||||||
// for (let j = 0; j < fees.length; j++) {
|
|
||||||
for (const fee of fees) {
|
|
||||||
if (i === this.feeLevels.length - 1) {
|
|
||||||
if (fee >= this.feeLevels[i]) {
|
|
||||||
total += 1;
|
|
||||||
}
|
|
||||||
} else if (fee >= this.feeLevels[i] && fee < this.feeLevels[i + 1]) {
|
|
||||||
total += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
series.push(total);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.mempoolVsizeFeesData = {
|
|
||||||
series: [fees],
|
|
||||||
labels: fees.map((d, i) => i)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
<div class="echarts" echarts [options]="mempoolStatsChartOption"></div>
|
@ -0,0 +1,162 @@
|
|||||||
|
import { Component, OnInit, Input, Inject, LOCALE_ID, ChangeDetectionStrategy } from '@angular/core';
|
||||||
|
import { formatDate } from '@angular/common';
|
||||||
|
import { EChartsOption } from 'echarts';
|
||||||
|
import { OnChanges } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-incoming-transactions-graph',
|
||||||
|
templateUrl: './incoming-transactions-graph.component.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class IncomingTransactionsGraphComponent implements OnInit, OnChanges {
|
||||||
|
@Input() data: any;
|
||||||
|
@Input() theme: string;
|
||||||
|
@Input() height: number | string = '200';
|
||||||
|
@Input() right: number | string = '10';
|
||||||
|
@Input() top: number | string = '20';
|
||||||
|
@Input() left: number | string = '50';
|
||||||
|
|
||||||
|
mempoolStatsChartOption: EChartsOption = {};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(LOCALE_ID) private locale: string,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnChanges(): void {
|
||||||
|
this.mountChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.mountChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
mountChart(): void {
|
||||||
|
this.mempoolStatsChartOption = {
|
||||||
|
grid: {
|
||||||
|
height: this.height,
|
||||||
|
right: this.right,
|
||||||
|
top: this.top,
|
||||||
|
left: this.left,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
position: (pos, params, el, elRect, size) => {
|
||||||
|
const obj = { top: -20 };
|
||||||
|
obj[['left', 'right'][+(pos[0] < size.viewSize[0] / 2)]] = 80;
|
||||||
|
return obj;
|
||||||
|
},
|
||||||
|
extraCssText: `background: transparent;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;`,
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
formatter: (axis: any) => {
|
||||||
|
if (axis.axisDimension === 'y') {
|
||||||
|
return `${Math.floor(axis.value)}`;
|
||||||
|
}
|
||||||
|
if (axis.axisDimension === 'x') {
|
||||||
|
return axis.value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatter: (params: any) => {
|
||||||
|
const colorSpan = (color: string) => `<div class="indicator" style="background-color: ` + color + `"></div>`;
|
||||||
|
let itemFormatted = '<div>' + params[0].axisValue + '</div>';
|
||||||
|
params.map((item: any, index: number) => {
|
||||||
|
if (index < 26) {
|
||||||
|
itemFormatted += `<div class="item">
|
||||||
|
${colorSpan(item.color)}
|
||||||
|
<div class="grow"></div>
|
||||||
|
<div class="value">${item.value}</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (this.theme !== '') {
|
||||||
|
return `<div class="tx-wrapper-tooltip-chart ${this.theme}">${itemFormatted}</div>`;
|
||||||
|
}
|
||||||
|
return `<div class="tx-wrapper-tooltip-chart">${itemFormatted}</div>`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: this.data.labels.map((value: any) => formatDate(value, 'HH:mm', this.locale)),
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
type: 'dotted',
|
||||||
|
color: '#ffffff66',
|
||||||
|
opacity: 0.25,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
data: this.data.series[0],
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
showSymbol: false,
|
||||||
|
lineStyle: {
|
||||||
|
width: 3,
|
||||||
|
},
|
||||||
|
markLine: {
|
||||||
|
silent: true,
|
||||||
|
symbol: 'none',
|
||||||
|
lineStyle: {
|
||||||
|
color: '#fff',
|
||||||
|
opacity: 0.75,
|
||||||
|
width: 2,
|
||||||
|
},
|
||||||
|
data: [{
|
||||||
|
yAxis: 1667,
|
||||||
|
label: {
|
||||||
|
show: false,
|
||||||
|
color: '#ffffff',
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
visualMap: {
|
||||||
|
show: false,
|
||||||
|
top: 50,
|
||||||
|
right: 10,
|
||||||
|
pieces: [{
|
||||||
|
gt: 0,
|
||||||
|
lte: 1667,
|
||||||
|
color: '#7CB342'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gt: 1667,
|
||||||
|
lte: 2000,
|
||||||
|
color: '#FDD835'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gt: 2000,
|
||||||
|
lte: 2500,
|
||||||
|
color: '#FFB300'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gt: 2500,
|
||||||
|
lte: 3000,
|
||||||
|
color: '#FB8C00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gt: 3000,
|
||||||
|
lte: 3500,
|
||||||
|
color: '#F4511E'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gt: 3500,
|
||||||
|
color: '#D81B60'
|
||||||
|
}],
|
||||||
|
outOfRange: {
|
||||||
|
color: '#999'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm">
|
<div class="col-md">
|
||||||
<table class="table table-borderless table-striped">
|
<table class="table table-borderless table-striped">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
@ -40,8 +40,8 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm">
|
<div class="col-md chart-container">
|
||||||
<app-fee-distribution-graph [feeRange]="mempoolBlock.feeRange"></app-fee-distribution-graph>
|
<app-fee-distribution-graph [data]="mempoolBlock.feeRange" ></app-fee-distribution-graph>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,11 +13,8 @@
|
|||||||
|
|
||||||
.fiat {
|
.fiat {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
display: block;
|
display: inline-block;
|
||||||
@media (min-width: 992px) {
|
margin-left: 10px;
|
||||||
display: inline-block;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
@ -38,4 +35,11 @@ h1 {
|
|||||||
float: left;
|
float: left;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chart-container{
|
||||||
|
margin: 20px auto;
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1 @@
|
|||||||
<app-chartist
|
<div class="echarts" echarts [options]="mempoolVsizeFeesOptions"></div>
|
||||||
*ngIf="mempoolVsizeFeesData"
|
|
||||||
[data]="mempoolVsizeFeesData"
|
|
||||||
[type]="'Line'"
|
|
||||||
[options]="mempoolVsizeFeesOptions">
|
|
||||||
</app-chartist>
|
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
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 { 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 * as Chartist from '@mempool/chartist';
|
|
||||||
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 { feeLevels, chartColors } from 'src/app/app.constants';
|
||||||
|
|
||||||
|
interface AxisObject {
|
||||||
|
axisDimension: string;
|
||||||
|
axisIndex: number;
|
||||||
|
seriesData: any;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-mempool-graph',
|
selector: 'app-mempool-graph',
|
||||||
@ -12,111 +20,50 @@ import { StorageService } from 'src/app/services/storage.service';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class MempoolGraphComponent implements OnInit, OnChanges {
|
export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||||
@Input() data;
|
@Input() data: any[];
|
||||||
|
@Input() limitFee = 300;
|
||||||
|
@Input() height: number | string = 200;
|
||||||
|
@Input() top: number | string = 20;
|
||||||
|
@Input() right: number | string = 10;
|
||||||
|
@Input() left: number | string = 75;
|
||||||
@Input() dateSpan = '2h';
|
@Input() dateSpan = '2h';
|
||||||
@Input() showLegend = true;
|
@Input() showLegend = true;
|
||||||
@Input() offsetX = 40;
|
|
||||||
@Input() small = false;
|
@Input() small = false;
|
||||||
|
|
||||||
mempoolVsizeFeesOptions: any;
|
|
||||||
mempoolVsizeFeesData: any;
|
mempoolVsizeFeesData: any;
|
||||||
|
mempoolVsizeFeesOptions: EChartsOption;
|
||||||
|
|
||||||
isMobile = window.innerWidth <= 767.98;
|
|
||||||
inverted: boolean;
|
inverted: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private vbytesPipe: VbytesPipe,
|
private vbytesPipe: VbytesPipe,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
@Inject(LOCALE_ID) private locale: string,
|
@Inject(LOCALE_ID) private locale: string,
|
||||||
private storageService: StorageService,
|
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
let labelHops = !this.showLegend ? 48 : 24;
|
this.mountFeeChart();
|
||||||
if (this.small) {
|
|
||||||
labelHops = labelHops / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isMobile) {
|
|
||||||
labelHops = 96;
|
|
||||||
}
|
|
||||||
|
|
||||||
const labelInterpolationFnc = (value: any, index: any) => {
|
|
||||||
switch (this.dateSpan) {
|
|
||||||
case '2h':
|
|
||||||
case '24h':
|
|
||||||
value = formatDate(value, 'HH:mm', this.locale);
|
|
||||||
break;
|
|
||||||
case '1w':
|
|
||||||
value = formatDate(value, 'dd/MM HH:mm', this.locale);
|
|
||||||
break;
|
|
||||||
case '1m':
|
|
||||||
case '3m':
|
|
||||||
case '6m':
|
|
||||||
case '1y':
|
|
||||||
value = formatDate(value, 'dd/MM', this.locale);
|
|
||||||
}
|
|
||||||
return index % labelHops === 0 ? value : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.mempoolVsizeFeesOptions = {
|
|
||||||
showArea: true,
|
|
||||||
showLine: false,
|
|
||||||
fullWidth: true,
|
|
||||||
showPoint: false,
|
|
||||||
stackedLine: !this.inverted,
|
|
||||||
low: 0,
|
|
||||||
axisX: {
|
|
||||||
labelInterpolationFnc: labelInterpolationFnc,
|
|
||||||
offset: this.offsetX,
|
|
||||||
},
|
|
||||||
axisY: {
|
|
||||||
labelInterpolationFnc: (value: number): any => this.vbytesPipe.transform(value, 2, 'vB', 'MvB', true),
|
|
||||||
offset: this.showLegend ? 160 : 60,
|
|
||||||
},
|
|
||||||
plugins: this.inverted ? [Chartist.plugins.ctTargetLine({ value: this.stateService.blockVSize })] : []
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.showLegend) {
|
|
||||||
const legendNames: string[] = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
|
|
||||||
250, 300, 350, 400].map((sat, i, arr) => {
|
|
||||||
if (sat === 400) {
|
|
||||||
return '350+';
|
|
||||||
}
|
|
||||||
if (i === 0) {
|
|
||||||
return '0 - 1';
|
|
||||||
}
|
|
||||||
return arr[i - 1] + ' - ' + sat;
|
|
||||||
});
|
|
||||||
// Only Liquid has lower than 1 sat/vb transactions
|
|
||||||
if (this.stateService.network !== 'liquid') {
|
|
||||||
legendNames.shift();
|
|
||||||
}
|
|
||||||
this.mempoolVsizeFeesOptions.plugins.push(
|
|
||||||
Chartist.plugins.legend({ legendNames: legendNames })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges() {
|
ngOnChanges() {
|
||||||
this.inverted = this.storageService.getValue('inverted-graph') === 'true';
|
// this.inverted = this.storageService.getValue('inverted-graph') === 'true';
|
||||||
this.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([]));
|
this.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([]));
|
||||||
|
this.mountFeeChart();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNewMempoolData(mempoolStats: OptimizedMempoolStats[]) {
|
handleNewMempoolData(mempoolStats: OptimizedMempoolStats[]) {
|
||||||
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);
|
|
||||||
|
|
||||||
// Only Liquid has lower than 1 sat/vb transactions
|
// Only Liquid has lower than 1 sat/vb transactions
|
||||||
if (this.stateService.network !== 'liquid') {
|
if (this.stateService.network !== 'liquid') {
|
||||||
finalArrayVbyte.shift();
|
finalArrayVByte.shift();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
labels: labels,
|
labels: labels,
|
||||||
series: finalArrayVbyte
|
series: finalArrayVByte
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,12 +81,128 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
feesArray.push(0);
|
feesArray.push(0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (this.inverted && finalArray.length) {
|
// if (this.inverted && finalArray.length) {
|
||||||
feesArray = feesArray.map((value, i) => value + finalArray[finalArray.length - 1][i]);
|
// feesArray = feesArray.map((value, i) => value + finalArray[finalArray.length - 1][i]);
|
||||||
}
|
// }
|
||||||
finalArray.push(feesArray);
|
finalArray.push(feesArray);
|
||||||
}
|
}
|
||||||
finalArray.reverse();
|
finalArray.reverse();
|
||||||
return finalArray;
|
return finalArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mountFeeChart(){
|
||||||
|
const { labels, series } = this.mempoolVsizeFeesData;
|
||||||
|
|
||||||
|
const legendNames: string[] = feeLevels.map((sat, i, arr) => {
|
||||||
|
if (sat > this.limitFee) { return `${this.limitFee}+`; }
|
||||||
|
if (i === 0) { return '0 - 1'; }
|
||||||
|
return arr[i - 1] + ' - ' + sat;
|
||||||
|
});
|
||||||
|
|
||||||
|
const yAxisSeries = series.map((value: Array<number>, index: number) => {
|
||||||
|
return {
|
||||||
|
name: labels[index].name,
|
||||||
|
type: 'line',
|
||||||
|
stack: 'total',
|
||||||
|
smooth: false,
|
||||||
|
lineStyle: {
|
||||||
|
width: 0,
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
showSymbol: false,
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 1,
|
||||||
|
color: chartColors[index],
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
focus: 'series'
|
||||||
|
},
|
||||||
|
markLine: {
|
||||||
|
symbol: 'none',
|
||||||
|
itemStyle: {
|
||||||
|
borderWidth: 0,
|
||||||
|
borderColor: 'none',
|
||||||
|
color: '#fff',
|
||||||
|
},
|
||||||
|
lineStyle: {
|
||||||
|
color: '#fff',
|
||||||
|
opacity: 0.75,
|
||||||
|
width: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: this.vbytesPipe.transform(value, 2, 'vB', 'MvB', true)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
this.mempoolVsizeFeesOptions = {
|
||||||
|
color: chartColors,
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
position: (pos, params, el, elRect, size) => {
|
||||||
|
const positions = { top: -20 };
|
||||||
|
positions[['left', 'right'][+(pos[0] < size.viewSize[0] / 2)]] = 80;
|
||||||
|
return positions;
|
||||||
|
},
|
||||||
|
extraCssText: `width: 150px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;`,
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
label: {
|
||||||
|
formatter: (axis: AxisObject) => {
|
||||||
|
if (axis.axisDimension === 'y') {
|
||||||
|
return `${this.vbytesPipe.transform(axis.value, 2, 'vB', 'MvB', true)}`;
|
||||||
|
}
|
||||||
|
if (axis.axisDimension === 'x') {
|
||||||
|
return axis.value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatter: (params: any) => {
|
||||||
|
const colorSpan = (index: number) => `<div class="indicator" style="background-color: ` + chartColors[index] + `"></div>`;
|
||||||
|
const legendName = (index: number) => legendNames[index];
|
||||||
|
let itemFormatted = '<div>' + params[0].axisValue + '</div>';
|
||||||
|
params.map((item: any, index: number) => {
|
||||||
|
if (feeLevels[index - 1] < this.limitFee) {
|
||||||
|
itemFormatted += `<div class="item">
|
||||||
|
${colorSpan(index - 1)} ${legendName(index)}
|
||||||
|
<div class="grow"></div>
|
||||||
|
<div class="value">${this.vbytesPipe.transform(item.value, 2, 'vB', 'MvB', true)}</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return `<div class="fees-wrapper-tooltip-chart">${itemFormatted}</div>`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
height: this.height,
|
||||||
|
right: this.right,
|
||||||
|
top: this.top,
|
||||||
|
left: this.left,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
data: labels.map((value: any) => formatDate(value, 'HH:mm', this.locale)),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
axisLabel: {
|
||||||
|
formatter: (value: number) => (`${this.vbytesPipe.transform(value, 2, 'vB', 'MvB', true)}`),
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
type: 'dotted',
|
||||||
|
color: '#ffffff66',
|
||||||
|
opacity: 0.25,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: yAxisSeries
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
.ct-legend {
|
|
||||||
top: 130px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column-reverse;
|
|
||||||
@media (min-width: 653px) {
|
|
||||||
top: 90px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ct-legend.inverted {
|
|
||||||
flex-direction: column !important;
|
|
||||||
}
|
|
@ -1,740 +0,0 @@
|
|||||||
import {
|
|
||||||
Component,
|
|
||||||
ElementRef,
|
|
||||||
Inject,
|
|
||||||
Input,
|
|
||||||
OnChanges,
|
|
||||||
OnDestroy,
|
|
||||||
OnInit,
|
|
||||||
PLATFORM_ID,
|
|
||||||
SimpleChanges,
|
|
||||||
ViewEncapsulation
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { isPlatformBrowser } from '@angular/common';
|
|
||||||
|
|
||||||
import * as Chartist from '@mempool/chartist';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Possible chart types
|
|
||||||
* @type {String}
|
|
||||||
*/
|
|
||||||
export type ChartType = 'Pie' | 'Bar' | 'Line';
|
|
||||||
|
|
||||||
export type ChartInterfaces =
|
|
||||||
| Chartist.IChartistPieChart
|
|
||||||
| Chartist.IChartistBarChart
|
|
||||||
| Chartist.IChartistLineChart;
|
|
||||||
export type ChartOptions =
|
|
||||||
| Chartist.IBarChartOptions
|
|
||||||
| Chartist.ILineChartOptions
|
|
||||||
| Chartist.IPieChartOptions;
|
|
||||||
export type ResponsiveOptionTuple = Chartist.IResponsiveOptionTuple<
|
|
||||||
ChartOptions
|
|
||||||
>;
|
|
||||||
export type ResponsiveOptions = ResponsiveOptionTuple[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represent a chart event.
|
|
||||||
* For possible values, check the Chartist docs.
|
|
||||||
*/
|
|
||||||
export interface ChartEvent {
|
|
||||||
[eventName: string]: (data: any) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-chartist',
|
|
||||||
template: '<ng-content></ng-content>',
|
|
||||||
styleUrls: ['./chartist.component.scss'],
|
|
||||||
encapsulation: ViewEncapsulation.None,
|
|
||||||
})
|
|
||||||
export class ChartistComponent implements OnInit, OnChanges, OnDestroy {
|
|
||||||
@Input()
|
|
||||||
// @ts-ignore
|
|
||||||
public data: Promise<Chartist.IChartistData> | Chartist.IChartistData;
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
@Input() public type: Promise<ChartType> | ChartType;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
// @ts-ignore
|
|
||||||
public options: Promise<Chartist.IChartOptions> | Chartist.IChartOptions;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
// @ts-ignore
|
|
||||||
public responsiveOptions: Promise<ResponsiveOptions> | ResponsiveOptions;
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
@Input() public events: ChartEvent;
|
|
||||||
|
|
||||||
isBrowser: boolean = isPlatformBrowser(this.platformId);
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
public chart: ChartInterfaces;
|
|
||||||
|
|
||||||
private element: HTMLElement;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
element: ElementRef,
|
|
||||||
@Inject(PLATFORM_ID) private platformId: any,
|
|
||||||
) {
|
|
||||||
this.element = element.nativeElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngOnInit(): Promise<ChartInterfaces> {
|
|
||||||
if (!this.isBrowser) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.type || !this.data) {
|
|
||||||
Promise.reject('Expected at least type and data.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.renderChart().then((chart) => {
|
|
||||||
if (this.events !== undefined) {
|
|
||||||
this.bindEvents(chart);
|
|
||||||
}
|
|
||||||
|
|
||||||
return chart;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngOnChanges(changes: SimpleChanges): void {
|
|
||||||
if (!this.isBrowser) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.update(changes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngOnDestroy(): void {
|
|
||||||
if (this.chart) {
|
|
||||||
this.chart.detach();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public renderChart(): Promise<ChartInterfaces> {
|
|
||||||
const promises: any[] = [
|
|
||||||
this.type,
|
|
||||||
this.element,
|
|
||||||
this.data,
|
|
||||||
this.options,
|
|
||||||
this.responsiveOptions
|
|
||||||
];
|
|
||||||
|
|
||||||
return Promise.all(promises).then((values) => {
|
|
||||||
const [type, ...args]: any = values;
|
|
||||||
|
|
||||||
if (!(type in Chartist)) {
|
|
||||||
throw new Error(`${type} is not a valid chart type`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.chart = (Chartist as any)[type](...args);
|
|
||||||
|
|
||||||
return this.chart;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public update(changes: SimpleChanges): void {
|
|
||||||
if (!this.chart || 'type' in changes) {
|
|
||||||
this.renderChart();
|
|
||||||
} else {
|
|
||||||
if (changes.data) {
|
|
||||||
this.data = changes.data.currentValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changes.options) {
|
|
||||||
this.options = changes.options.currentValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
(this.chart as any).update(this.data, this.options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bindEvents(chart: any): void {
|
|
||||||
for (const event of Object.keys(this.events)) {
|
|
||||||
chart.on(event, this.events[event]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Chartist.js plugin to display a "target" or "goal" line across the chart.
|
|
||||||
* Only tested with bar charts. Works for horizontal and vertical bars.
|
|
||||||
*/
|
|
||||||
(function(window, document, Chartist) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const defaultOptions = {
|
|
||||||
// The class name so you can style the text
|
|
||||||
className: 'ct-target-line',
|
|
||||||
// The axis to draw the line. y == vertical bars, x == horizontal
|
|
||||||
axis: 'y',
|
|
||||||
// What value the target line should be drawn at
|
|
||||||
value: null
|
|
||||||
};
|
|
||||||
|
|
||||||
Chartist.plugins = Chartist.plugins || {};
|
|
||||||
|
|
||||||
Chartist.plugins.ctTargetLine = function (options: any) {
|
|
||||||
options = Chartist.extend({}, defaultOptions, options);
|
|
||||||
return function ctTargetLine (chart: any) {
|
|
||||||
|
|
||||||
chart.on('created', function(context: any) {
|
|
||||||
const projectTarget = {
|
|
||||||
y: function (chartRect: any, bounds: any, value: any) {
|
|
||||||
const targetLineY = chartRect.y1 - (chartRect.height() / bounds.max * value);
|
|
||||||
|
|
||||||
return {
|
|
||||||
x1: chartRect.x1,
|
|
||||||
x2: chartRect.x2,
|
|
||||||
y1: targetLineY,
|
|
||||||
y2: targetLineY
|
|
||||||
};
|
|
||||||
},
|
|
||||||
x: function (chartRect: any, bounds: any, value: any) {
|
|
||||||
const targetLineX = chartRect.x1 + (chartRect.width() / bounds.max * value);
|
|
||||||
|
|
||||||
return {
|
|
||||||
x1: targetLineX,
|
|
||||||
x2: targetLineX,
|
|
||||||
y1: chartRect.y1,
|
|
||||||
y2: chartRect.y2
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// @ts-ignore
|
|
||||||
const targetLine = projectTarget[options.axis](context.chartRect, context.bounds, options.value);
|
|
||||||
|
|
||||||
context.svg.elem('line', targetLine, options.className);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
}(null, null, Chartist));
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Chartist.js plugin to display a data label on top of the points in a line chart.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
/* global Chartist */
|
|
||||||
(function(window, document, Chartist) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const defaultOptions = {
|
|
||||||
labelClass: 'ct-label',
|
|
||||||
labelOffset: {
|
|
||||||
x: 0,
|
|
||||||
y: -10
|
|
||||||
},
|
|
||||||
textAnchor: 'middle',
|
|
||||||
align: 'center',
|
|
||||||
labelInterpolationFnc: Chartist.noop
|
|
||||||
};
|
|
||||||
|
|
||||||
const labelPositionCalculation = {
|
|
||||||
point: function(data: any) {
|
|
||||||
return {
|
|
||||||
x: data.x,
|
|
||||||
y: data.y
|
|
||||||
};
|
|
||||||
},
|
|
||||||
bar: {
|
|
||||||
left: function(data: any) {
|
|
||||||
return {
|
|
||||||
x: data.x1,
|
|
||||||
y: data.y1
|
|
||||||
};
|
|
||||||
},
|
|
||||||
center: function(data: any) {
|
|
||||||
return {
|
|
||||||
x: data.x1 + (data.x2 - data.x1) / 2,
|
|
||||||
y: data.y1
|
|
||||||
};
|
|
||||||
},
|
|
||||||
right: function(data: any) {
|
|
||||||
return {
|
|
||||||
x: data.x2,
|
|
||||||
y: data.y1
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Chartist.plugins = Chartist.plugins || {};
|
|
||||||
Chartist.plugins.ctPointLabels = function(options: any) {
|
|
||||||
|
|
||||||
options = Chartist.extend({}, defaultOptions, options);
|
|
||||||
|
|
||||||
function addLabel(position: any, data: any) {
|
|
||||||
// if x and y exist concat them otherwise output only the existing value
|
|
||||||
const value = data.value.x !== undefined && data.value.y ?
|
|
||||||
(data.value.x + ', ' + data.value.y) :
|
|
||||||
data.value.y || data.value.x;
|
|
||||||
|
|
||||||
data.group.elem('text', {
|
|
||||||
x: position.x + options.labelOffset.x,
|
|
||||||
y: position.y + options.labelOffset.y,
|
|
||||||
style: 'text-anchor: ' + options.textAnchor
|
|
||||||
}, options.labelClass).text(options.labelInterpolationFnc(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
return function ctPointLabels(chart: any) {
|
|
||||||
if (chart instanceof Chartist.Line || chart instanceof Chartist.Bar) {
|
|
||||||
chart.on('draw', function(data: any) {
|
|
||||||
// @ts-ignore
|
|
||||||
const positonCalculator = labelPositionCalculation[data.type]
|
|
||||||
// @ts-ignore
|
|
||||||
&& labelPositionCalculation[data.type][options.align] || labelPositionCalculation[data.type];
|
|
||||||
if (positonCalculator) {
|
|
||||||
addLabel(positonCalculator(data), data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
}(null, null, Chartist));
|
|
||||||
|
|
||||||
const defaultOptions = {
|
|
||||||
className: '',
|
|
||||||
classNames: false,
|
|
||||||
removeAll: false,
|
|
||||||
legendNames: false,
|
|
||||||
clickable: true,
|
|
||||||
onClick: null,
|
|
||||||
position: 'top'
|
|
||||||
};
|
|
||||||
|
|
||||||
Chartist.plugins.legend = function (options: any) {
|
|
||||||
let cachedDOMPosition;
|
|
||||||
let cacheInactiveLegends: { [key:number]: boolean } = {};
|
|
||||||
// Catch invalid options
|
|
||||||
if (options && options.position) {
|
|
||||||
if (!(options.position === 'top' || options.position === 'bottom' || options.position instanceof HTMLElement)) {
|
|
||||||
throw Error('The position you entered is not a valid position');
|
|
||||||
}
|
|
||||||
if (options.position instanceof HTMLElement) {
|
|
||||||
// Detatch DOM element from options object, because Chartist.extend
|
|
||||||
// currently chokes on circular references present in HTMLElements
|
|
||||||
cachedDOMPosition = options.position;
|
|
||||||
delete options.position;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
options = Chartist.extend({}, defaultOptions, options);
|
|
||||||
|
|
||||||
if (cachedDOMPosition) {
|
|
||||||
// Reattatch the DOM Element position if it was removed before
|
|
||||||
options.position = cachedDOMPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
return function legend(chart: any) {
|
|
||||||
|
|
||||||
var isSelfUpdate = false;
|
|
||||||
|
|
||||||
chart.on('created', function (data: any) {
|
|
||||||
|
|
||||||
const useLabels = chart instanceof Chartist.Pie && chart.data.labels && chart.data.labels.length;
|
|
||||||
const legendNames = getLegendNames(useLabels);
|
|
||||||
var dirtyChartData = (chart.data.series.length < legendNames.length);
|
|
||||||
|
|
||||||
if (isSelfUpdate || dirtyChartData)
|
|
||||||
return;
|
|
||||||
|
|
||||||
function removeLegendElement() {
|
|
||||||
const legendElement = chart.container.querySelector('.ct-legend');
|
|
||||||
if (legendElement) {
|
|
||||||
legendElement.parentNode.removeChild(legendElement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a unique className for each series so that when a series is removed,
|
|
||||||
// the other series still have the same color.
|
|
||||||
function setSeriesClassNames() {
|
|
||||||
chart.data.series = chart.data.series.map(function (series: any, seriesIndex: any) {
|
|
||||||
if (typeof series !== 'object') {
|
|
||||||
series = {
|
|
||||||
value: series
|
|
||||||
};
|
|
||||||
}
|
|
||||||
series.className = series.className || chart.options.classNames.series + '-' + Chartist.alphaNumerate(seriesIndex);
|
|
||||||
return series;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createLegendElement() {
|
|
||||||
const legendElement = document.createElement('ul');
|
|
||||||
legendElement.className = 'ct-legend';
|
|
||||||
const inverted = localStorage.getItem('inverted-graph') === 'true';
|
|
||||||
if (inverted){
|
|
||||||
legendElement.classList.add('inverted');
|
|
||||||
}
|
|
||||||
if (chart instanceof Chartist.Pie) {
|
|
||||||
legendElement.classList.add('ct-legend-inside');
|
|
||||||
}
|
|
||||||
if (typeof options.className === 'string' && options.className.length > 0) {
|
|
||||||
legendElement.classList.add(options.className);
|
|
||||||
}
|
|
||||||
if (chart.options.width) {
|
|
||||||
legendElement.style.cssText = 'width: ' + chart.options.width + 'px;margin: 0 auto;';
|
|
||||||
}
|
|
||||||
return legendElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the right array to use for generating the legend.
|
|
||||||
function getLegendNames(useLabels: any) {
|
|
||||||
return options.legendNames || (useLabels ? chart.data.labels : chart.data.series);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the array that associates series with legends.
|
|
||||||
// -1 indicates that there is no legend associated with it.
|
|
||||||
function initSeriesMetadata(useLabels: any) {
|
|
||||||
const seriesMetadata = new Array(chart.data.series.length);
|
|
||||||
for (let i = 0; i < chart.data.series.length; i++) {
|
|
||||||
seriesMetadata[i] = {
|
|
||||||
data: chart.data.series[i],
|
|
||||||
label: useLabels ? chart.data.labels[i] : null,
|
|
||||||
legend: -1
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return seriesMetadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createNameElement(i: any, legendText: any, classNamesViable: any) {
|
|
||||||
const li = document.createElement('li');
|
|
||||||
li.classList.add('ct-series-' + i);
|
|
||||||
// Append specific class to a legend element, if viable classes are given
|
|
||||||
if (classNamesViable) {
|
|
||||||
li.classList.add(options.classNames[i]);
|
|
||||||
}
|
|
||||||
li.setAttribute('data-legend', i);
|
|
||||||
li.textContent = legendText;
|
|
||||||
return li;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append the legend element to the DOM
|
|
||||||
function appendLegendToDOM(legendElement: any) {
|
|
||||||
if (!(options.position instanceof HTMLElement)) {
|
|
||||||
switch (options.position) {
|
|
||||||
case 'top':
|
|
||||||
chart.container.insertBefore(legendElement, chart.container.childNodes[0]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'bottom':
|
|
||||||
chart.container.insertBefore(legendElement, null);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Appends the legend element as the last child of a given HTMLElement
|
|
||||||
options.position.insertBefore(legendElement, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateChart(newSeries: any, newLabels:any, useLabels: any) {
|
|
||||||
chart.data.series = newSeries;
|
|
||||||
if (useLabels) {
|
|
||||||
chart.data.labels = newLabels;
|
|
||||||
}
|
|
||||||
|
|
||||||
isSelfUpdate = true;
|
|
||||||
chart.update();
|
|
||||||
isSelfUpdate = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addClickHandler(legendElement: any, legends: any, seriesMetadata: any, useLabels: any) {
|
|
||||||
legendElement.addEventListener('click', function(e: any) {
|
|
||||||
const li = e.target;
|
|
||||||
if (li.parentNode !== legendElement || !li.hasAttribute('data-legend'))
|
|
||||||
return;
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const legendIndex = parseInt(li.getAttribute('data-legend'));
|
|
||||||
const legend = legends[legendIndex];
|
|
||||||
|
|
||||||
const activateLegend = (_legendIndex: number): void => {
|
|
||||||
legends[_legendIndex].active = true;
|
|
||||||
legendElement.childNodes[_legendIndex].classList.remove('inactive');
|
|
||||||
|
|
||||||
cacheInactiveLegends[_legendIndex] = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const deactivateLegend = (_legendIndex: number): void => {
|
|
||||||
legends[_legendIndex].active = false;
|
|
||||||
legendElement.childNodes[_legendIndex].classList.add('inactive');
|
|
||||||
cacheInactiveLegends[_legendIndex] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = legends.length - 1; i >= 0; i--) {
|
|
||||||
if (i >= legendIndex) {
|
|
||||||
if (!legend.active) activateLegend(i);
|
|
||||||
} else {
|
|
||||||
if (legend.active) deactivateLegend(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Make sure all values are undefined (falsy) when clicking the first legend
|
|
||||||
// After clicking the first legend all indices should be falsy
|
|
||||||
if (legendIndex === 0) cacheInactiveLegends = {};
|
|
||||||
|
|
||||||
const newSeries = [];
|
|
||||||
const newLabels = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < seriesMetadata.length; i++) {
|
|
||||||
if (seriesMetadata[i].legend !== -1 && legends[seriesMetadata[i].legend].active) {
|
|
||||||
newSeries.push(seriesMetadata[i].data);
|
|
||||||
newLabels.push(seriesMetadata[i].label);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateChart(newSeries, newLabels, useLabels);
|
|
||||||
|
|
||||||
if (options.onClick) {
|
|
||||||
options.onClick(chart, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
removeLegendElement();
|
|
||||||
|
|
||||||
const legendElement = createLegendElement();
|
|
||||||
const seriesMetadata = initSeriesMetadata(useLabels);
|
|
||||||
const legends: any = [];
|
|
||||||
|
|
||||||
// Check if given class names are viable to append to legends
|
|
||||||
const classNamesViable = Array.isArray(options.classNames) && options.classNames.length === legendNames.length;
|
|
||||||
|
|
||||||
var activeSeries = [];
|
|
||||||
var activeLabels = [];
|
|
||||||
|
|
||||||
// Loop through all legends to set each name in a list item.
|
|
||||||
legendNames.forEach(function (legend: any, i: any) {
|
|
||||||
const legendText = legend.name || legend;
|
|
||||||
const legendSeries = legend.series || [i];
|
|
||||||
|
|
||||||
const li = createNameElement(i, legendText, classNamesViable);
|
|
||||||
// If the value is undefined or false, isActive is true
|
|
||||||
const isActive: boolean = !cacheInactiveLegends[i];
|
|
||||||
if (isActive) {
|
|
||||||
activeSeries.push(seriesMetadata[i].data);
|
|
||||||
activeLabels.push(seriesMetadata[i].label);
|
|
||||||
} else {
|
|
||||||
li.classList.add('inactive');
|
|
||||||
}
|
|
||||||
legendElement.appendChild(li);
|
|
||||||
|
|
||||||
legendSeries.forEach(function(seriesIndex: any) {
|
|
||||||
seriesMetadata[seriesIndex].legend = i;
|
|
||||||
});
|
|
||||||
|
|
||||||
legends.push({
|
|
||||||
text: legendText,
|
|
||||||
series: legendSeries,
|
|
||||||
active: isActive
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
appendLegendToDOM(legendElement);
|
|
||||||
|
|
||||||
if (options.clickable) {
|
|
||||||
setSeriesClassNames();
|
|
||||||
addClickHandler(legendElement, legends, seriesMetadata, useLabels);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateChart(activeSeries, activeLabels, useLabels);
|
|
||||||
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
Chartist.plugins.tooltip = function (options: any) {
|
|
||||||
options = Chartist.extend({}, defaultOptions, options);
|
|
||||||
|
|
||||||
return function tooltip(chart: any) {
|
|
||||||
let tooltipSelector = options.pointClass;
|
|
||||||
if (chart instanceof Chartist.Bar) {
|
|
||||||
tooltipSelector = 'ct-bar';
|
|
||||||
} else if (chart instanceof Chartist.Pie) {
|
|
||||||
// Added support for donut graph
|
|
||||||
if (chart.options.donut) {
|
|
||||||
tooltipSelector = 'ct-slice-donut';
|
|
||||||
} else {
|
|
||||||
tooltipSelector = 'ct-slice-pie';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const $chart = chart.container;
|
|
||||||
let $toolTip = $chart.querySelector('.chartist-tooltip');
|
|
||||||
if (!$toolTip) {
|
|
||||||
$toolTip = document.createElement('div');
|
|
||||||
$toolTip.className = (!options.class) ? 'chartist-tooltip' : 'chartist-tooltip ' + options.class;
|
|
||||||
if (!options.appendToBody) {
|
|
||||||
$chart.appendChild($toolTip);
|
|
||||||
} else {
|
|
||||||
document.body.appendChild($toolTip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let height = $toolTip.offsetHeight;
|
|
||||||
let width = $toolTip.offsetWidth;
|
|
||||||
|
|
||||||
hide($toolTip);
|
|
||||||
|
|
||||||
function on(event: any, selector: any, callback: any) {
|
|
||||||
$chart.addEventListener(event, function (e: any) {
|
|
||||||
if (!selector || hasClass(e.target, selector)) {
|
|
||||||
callback(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
on('mouseover', tooltipSelector, function (event: any) {
|
|
||||||
const $point = event.target;
|
|
||||||
let tooltipText = '';
|
|
||||||
|
|
||||||
const isPieChart = (chart instanceof Chartist.Pie) ? $point : $point.parentNode;
|
|
||||||
const seriesName = (isPieChart) ? $point.parentNode.getAttribute('ct:meta') || $point.parentNode.getAttribute('ct:series-name') : '';
|
|
||||||
let meta = $point.getAttribute('ct:meta') || seriesName || '';
|
|
||||||
const hasMeta = !!meta;
|
|
||||||
let value = $point.getAttribute('ct:value');
|
|
||||||
|
|
||||||
if (options.transformTooltipTextFnc && typeof options.transformTooltipTextFnc === 'function') {
|
|
||||||
value = options.transformTooltipTextFnc(value, $point.parentNode.getAttribute('class'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.tooltipFnc && typeof options.tooltipFnc === 'function') {
|
|
||||||
tooltipText = options.tooltipFnc(meta, value);
|
|
||||||
} else {
|
|
||||||
if (options.metaIsHTML) {
|
|
||||||
const txt = document.createElement('textarea');
|
|
||||||
txt.innerHTML = meta;
|
|
||||||
meta = txt.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
meta = '<span class="chartist-tooltip-meta">' + meta + '</span>';
|
|
||||||
|
|
||||||
if (hasMeta) {
|
|
||||||
tooltipText += meta + '<br>';
|
|
||||||
} else {
|
|
||||||
// For Pie Charts also take the labels into account
|
|
||||||
// Could add support for more charts here as well!
|
|
||||||
if (chart instanceof Chartist.Pie) {
|
|
||||||
const label = next($point, 'ct-label');
|
|
||||||
if (label) {
|
|
||||||
tooltipText += text(label) + '<br>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
if (options.currency) {
|
|
||||||
if (options.currencyFormatCallback != undefined) {
|
|
||||||
value = options.currencyFormatCallback(value, options);
|
|
||||||
} else {
|
|
||||||
value = options.currency + value.replace(/(\d)(?=(\d{3})+(?:\.\d+)?$)/g, '$1,');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
value = '<span class="chartist-tooltip-value">' + value + '</span>';
|
|
||||||
tooltipText += value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tooltipText) {
|
|
||||||
$toolTip.innerHTML = tooltipText;
|
|
||||||
setPosition(event);
|
|
||||||
show($toolTip);
|
|
||||||
|
|
||||||
// Remember height and width to avoid wrong position in IE
|
|
||||||
height = $toolTip.offsetHeight;
|
|
||||||
width = $toolTip.offsetWidth;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
on('mouseout', tooltipSelector, function () {
|
|
||||||
hide($toolTip);
|
|
||||||
});
|
|
||||||
|
|
||||||
on('mousemove', null, function (event: any) {
|
|
||||||
if (false === options.anchorToPoint) {
|
|
||||||
setPosition(event);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function setPosition(event: any) {
|
|
||||||
height = height || $toolTip.offsetHeight;
|
|
||||||
width = width || $toolTip.offsetWidth;
|
|
||||||
const offsetX = - width / 2 + options.tooltipOffset.x
|
|
||||||
const offsetY = - height + options.tooltipOffset.y;
|
|
||||||
let anchorX, anchorY;
|
|
||||||
|
|
||||||
if (!options.appendToBody) {
|
|
||||||
const box = $chart.getBoundingClientRect();
|
|
||||||
const left = event.pageX - box.left - window.pageXOffset ;
|
|
||||||
const top = event.pageY - box.top - window.pageYOffset ;
|
|
||||||
|
|
||||||
if (true === options.anchorToPoint && event.target.x2 && event.target.y2) {
|
|
||||||
anchorX = parseInt(event.target.x2.baseVal.value);
|
|
||||||
anchorY = parseInt(event.target.y2.baseVal.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
$toolTip.style.top = (anchorY || top) + offsetY + 'px';
|
|
||||||
$toolTip.style.left = (anchorX || left) + offsetX + 'px';
|
|
||||||
} else {
|
|
||||||
$toolTip.style.top = event.pageY + offsetY + 'px';
|
|
||||||
$toolTip.style.left = event.pageX + offsetX + 'px';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Chartist.plugins.ctPointLabels = (options) => {
|
|
||||||
return function ctPointLabels(chart) {
|
|
||||||
const defaultOptions2 = {
|
|
||||||
labelClass: 'ct-point-label',
|
|
||||||
labelOffset: {
|
|
||||||
x: 0,
|
|
||||||
y: -7
|
|
||||||
},
|
|
||||||
textAnchor: 'middle'
|
|
||||||
};
|
|
||||||
options = Chartist.extend({}, defaultOptions2, options);
|
|
||||||
|
|
||||||
if (chart instanceof Chartist.Line) {
|
|
||||||
chart.on('draw', (data) => {
|
|
||||||
if (data.type === 'point') {
|
|
||||||
data.group.elem('text', {
|
|
||||||
x: data.x + options.labelOffset.x,
|
|
||||||
y: data.y + options.labelOffset.y,
|
|
||||||
style: 'text-anchor: ' + options.textAnchor
|
|
||||||
}, options.labelClass).text(options.labelInterpolationFnc(data.value.y)); // 07.11.17 added ".y"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
function show(element: any) {
|
|
||||||
if (!hasClass(element, 'tooltip-show')) {
|
|
||||||
element.className = element.className + ' tooltip-show';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function hide(element: any) {
|
|
||||||
const regex = new RegExp('tooltip-show' + '\\s*', 'gi');
|
|
||||||
element.className = element.className.replace(regex, '').trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasClass(element: any, className: any) {
|
|
||||||
return (' ' + element.getAttribute('class') + ' ').indexOf(' ' + className + ' ') > -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function next(element: any, className: any) {
|
|
||||||
do {
|
|
||||||
element = element.nextSibling;
|
|
||||||
} while (element && !hasClass(element, className));
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
function text(element: any) {
|
|
||||||
return element.innerText || element.textContent;
|
|
||||||
}
|
|
@ -36,12 +36,12 @@
|
|||||||
<input ngbButton type="radio" [value]="'1y'" [routerLink]="['/graphs' | relativeUrl]" fragment="1y"> 1Y
|
<input ngbButton type="radio" [value]="'1y'" [routerLink]="['/graphs' | relativeUrl]" fragment="1y"> 1Y
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<button (click)="invertGraph()" class="btn btn-primary btn-sm ml-2 d-none d-md-inline"><fa-icon [icon]="['fas', 'exchange-alt']" [rotate]="90" [fixedWidth]="true" i18n-title="statistics.component-invert.title" title="Invert"></fa-icon></button>
|
<!-- <button (click)="invertGraph()" class="btn btn-primary btn-sm ml-2 d-none d-md-inline"><fa-icon [icon]="['fas', 'exchange-alt']" [rotate]="90" [fixedWidth]="true" i18n-title="statistics.component-invert.title" title="Invert"></fa-icon></button> -->
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div style="height: 600px;">
|
<div class="incoming-transactions-graph">
|
||||||
<app-mempool-graph dir="ltr" [data]="mempoolStats" [dateSpan]="radioGroupForm.controls.dateSpan.value"></app-mempool-graph>
|
<app-mempool-graph dir="ltr" [limitFee]="1200" [height]="550" [left]="60" [data]="mempoolStats"></app-mempool-graph>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -53,12 +53,8 @@
|
|||||||
<i class="fa fa-area-chart"></i> <span i18n="statistics.transaction-vbytes-per-second">Transaction vBytes per second (vB/s)</span>
|
<i class="fa fa-area-chart"></i> <span i18n="statistics.transaction-vbytes-per-second">Transaction vBytes per second (vB/s)</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div style="height: 600px;">
|
<div class="incoming-transactions-graph">
|
||||||
<app-chartist
|
<app-incoming-transactions-graph [height]="500" [data]="mempoolTransactionsWeightPerSecondData"></app-incoming-transactions-graph>
|
||||||
[data]="mempoolTransactionsWeightPerSecondData"
|
|
||||||
[type]="'Line'"
|
|
||||||
[options]="transactionsWeightPerSecondOptions">
|
|
||||||
</app-chartist>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -56,4 +56,8 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
height: 80vh;
|
height: 80vh;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.incoming-transactions-graph {
|
||||||
|
height: 600px;
|
||||||
|
}
|
||||||
|
@ -9,7 +9,6 @@ import { OptimizedMempoolStats } from '../../interfaces/node-api.interface';
|
|||||||
import { WebsocketService } from '../../services/websocket.service';
|
import { WebsocketService } from '../../services/websocket.service';
|
||||||
import { ApiService } from '../../services/api.service';
|
import { ApiService } from '../../services/api.service';
|
||||||
|
|
||||||
import * as Chartist from '@mempool/chartist';
|
|
||||||
import { StateService } from 'src/app/services/state.service';
|
import { StateService } from 'src/app/services/state.service';
|
||||||
import { SeoService } from 'src/app/services/seo.service';
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
import { StorageService } from 'src/app/services/storage.service';
|
import { StorageService } from 'src/app/services/storage.service';
|
||||||
@ -31,8 +30,6 @@ export class StatisticsComponent implements OnInit {
|
|||||||
mempoolUnconfirmedTransactionsData: any;
|
mempoolUnconfirmedTransactionsData: any;
|
||||||
mempoolTransactionsWeightPerSecondData: any;
|
mempoolTransactionsWeightPerSecondData: any;
|
||||||
|
|
||||||
transactionsWeightPerSecondOptions: any;
|
|
||||||
|
|
||||||
radioGroupForm: FormGroup;
|
radioGroupForm: FormGroup;
|
||||||
inverted: boolean;
|
inverted: boolean;
|
||||||
graphWindowPreference: String;
|
graphWindowPreference: String;
|
||||||
@ -64,43 +61,6 @@ export class StatisticsComponent implements OnInit {
|
|||||||
dateSpan: this.graphWindowPreference
|
dateSpan: this.graphWindowPreference
|
||||||
});
|
});
|
||||||
|
|
||||||
const labelInterpolationFnc = (value: any, index: any) => {
|
|
||||||
switch (this.graphWindowPreference) {
|
|
||||||
case '2h':
|
|
||||||
case '24h':
|
|
||||||
value = formatDate(value, 'HH:mm', this.locale);
|
|
||||||
break;
|
|
||||||
case '1w':
|
|
||||||
value = formatDate(value, 'dd/MM HH:mm', this.locale);
|
|
||||||
break;
|
|
||||||
case '1m':
|
|
||||||
case '3m':
|
|
||||||
case '6m':
|
|
||||||
case '1y':
|
|
||||||
value = formatDate(value, 'dd/MM', this.locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
return index % labelHops === 0 ? value : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.transactionsWeightPerSecondOptions = {
|
|
||||||
showArea: false,
|
|
||||||
showLine: true,
|
|
||||||
showPoint: false,
|
|
||||||
low: 0,
|
|
||||||
axisY: {
|
|
||||||
offset: 40
|
|
||||||
},
|
|
||||||
axisX: {
|
|
||||||
labelInterpolationFnc: labelInterpolationFnc
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
Chartist.plugins.ctTargetLine({
|
|
||||||
value: 1667
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
this.route
|
this.route
|
||||||
.fragment
|
.fragment
|
||||||
.subscribe((fragment) => {
|
.subscribe((fragment) => {
|
||||||
|
@ -4,12 +4,10 @@
|
|||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tv-container">
|
<div class="tv-container" *ngIf="mempoolStats.length">
|
||||||
|
<div class="chart-holder" >
|
||||||
<div class="chart-holder" *ngIf="mempoolStats.length">
|
<app-mempool-graph dir="ltr" [data]="mempoolStats" [limitFee]="1200" [height]="600"></app-mempool-graph>
|
||||||
<app-mempool-graph dir="ltr" [data]="mempoolStats"></app-mempool-graph>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="blockchain-wrapper">
|
<div class="blockchain-wrapper">
|
||||||
<div class="position-container">
|
<div class="position-container">
|
||||||
<app-mempool-blocks></app-mempool-blocks>
|
<app-mempool-blocks></app-mempool-blocks>
|
||||||
|
@ -16,30 +16,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chart-holder {
|
.chart-holder {
|
||||||
height: calc(100vh - 270px);
|
height: 650px;
|
||||||
min-height: 525px;
|
width: 100%;
|
||||||
padding-left: 20px;
|
margin: 30px auto;
|
||||||
width: 98.5%;
|
|
||||||
padding-top: 20px;
|
|
||||||
@media(min-width: 992px){
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
||||||
@media(min-height: 800px){
|
|
||||||
padding-top: 60px !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.blockchain-wrapper {
|
.blockchain-wrapper {
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 240px;
|
min-height: 240px;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -20px;
|
top: -20px;
|
||||||
@media(min-height: 800px) {
|
@media(min-height: 800px) {
|
||||||
top: 10px;
|
top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.position-container {
|
.position-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
@ -89,4 +80,4 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
@ -47,8 +47,8 @@
|
|||||||
<ng-container *ngTemplateOutlet="mempoolTable; context: { $implicit: mempoolInfoData }"></ng-container>
|
<ng-container *ngTemplateOutlet="mempoolTable; context: { $implicit: mempoolInfoData }"></ng-container>
|
||||||
<hr>
|
<hr>
|
||||||
</div>
|
</div>
|
||||||
<div class="mempool-graph" *ngIf="(mempoolStats$ | async) as mempoolStats">
|
<div class="mempool-graph" *ngIf="(mempoolStats$ | async) as mempoolStats; else loadingSpinner">
|
||||||
<app-mempool-graph [data]="mempoolStats.mempool" [showLegend]="false" [offsetX]="20" [small]="true"></app-mempool-graph>
|
<app-mempool-graph [data]="mempoolStats.mempool"></app-mempool-graph>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -59,12 +59,8 @@
|
|||||||
<ng-container *ngTemplateOutlet="txPerSecond; context: { $implicit: mempoolInfoData }"></ng-container>
|
<ng-container *ngTemplateOutlet="txPerSecond; context: { $implicit: mempoolInfoData }"></ng-container>
|
||||||
<br>
|
<br>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="mempool-graph" *ngIf="(mempoolStats$ | async) as mempoolStats">
|
<div class="mempool-graph" *ngIf="(mempoolStats$ | async) as mempoolStats; else loadingSpinner">
|
||||||
<app-chartist
|
<app-incoming-transactions-graph [data]="mempoolStats.weightPerSecond"></app-incoming-transactions-graph>
|
||||||
[data]="mempoolStats.weightPerSecond"
|
|
||||||
[type]="'Line'"
|
|
||||||
[options]="transactionsWeightPerSecondOptions">
|
|
||||||
</app-chartist>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -197,13 +193,14 @@
|
|||||||
</span>
|
</span>
|
||||||
<ng-template #inSync>
|
<ng-template #inSync>
|
||||||
<div class="progress inc-tx-progress-bar">
|
<div class="progress inc-tx-progress-bar">
|
||||||
<div class="progress-bar {{ mempoolInfoData.value.progressClass }}" role="progressbar" [ngStyle]="{'width': mempoolInfoData.value.progressWidth}"> </div>
|
<div class="progress-bar" role="progressbar" [ngStyle]="{'width': mempoolInfoData.value.progressWidth, 'background-color': mempoolInfoData.value.progressColor}"> </div>
|
||||||
<div class="progress-text">{{ mempoolInfoData.value.vBytesPerSecond | ceil | number }} <ng-container i18n="shared.vbytes-per-second|vB/s">vB/s</ng-container></div>
|
<div class="progress-text">{{ mempoolInfoData.value.vBytesPerSecond | ceil | number }} <ng-container i18n="shared.vbytes-per-second|vB/s">vB/s</ng-container></div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
|
||||||
<ng-template #difficultyEpoch>
|
<ng-template #difficultyEpoch>
|
||||||
<div class="main-title" i18n="dashboard.difficulty-adjustment">Difficulty Adjustment</div>
|
<div class="main-title" i18n="dashboard.difficulty-adjustment">Difficulty Adjustment</div>
|
||||||
<div class="card-wrapper">
|
<div class="card-wrapper">
|
||||||
@ -228,11 +225,11 @@
|
|||||||
<ng-template #arrowDownDifficulty >
|
<ng-template #arrowDownDifficulty >
|
||||||
<fa-icon class="retarget-sign" [icon]="['fas', 'caret-down']" [fixedWidth]="true"></fa-icon>
|
<fa-icon class="retarget-sign" [icon]="['fas', 'caret-down']" [fixedWidth]="true"></fa-icon>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
{{ epochData.change | absolute | number: '1.2-2' }}
|
{{ epochData.change | absolute | number: '1.2-2' }}
|
||||||
<span class="symbol">%</span>
|
<span class="symbol">%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="symbol">
|
<div class="symbol">
|
||||||
<span i18n="difficulty-box.previous">Previous</span>:
|
<span i18n="difficulty-box.previous">Previous</span>:
|
||||||
<span [ngStyle]="{'color': epochData.colorPreviousAdjustments}">
|
<span [ngStyle]="{'color': epochData.colorPreviousAdjustments}">
|
||||||
<span *ngIf="epochData.previousRetarget > 0; else arrowDownPreviousDifficulty" >
|
<span *ngIf="epochData.previousRetarget > 0; else arrowDownPreviousDifficulty" >
|
||||||
<fa-icon class="previous-retarget-sign" [icon]="['fas', 'caret-up']" [fixedWidth]="true"></fa-icon>
|
<fa-icon class="previous-retarget-sign" [icon]="['fas', 'caret-up']" [fixedWidth]="true"></fa-icon>
|
||||||
@ -257,6 +254,12 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
|
||||||
|
<ng-template #loadingSpinner>
|
||||||
|
<div class="text-center loadingGraphs">
|
||||||
|
<div class="spinner-border text-light"></div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #loadingDifficulty>
|
<ng-template #loadingDifficulty>
|
||||||
<div class="difficulty-skeleton loading-container">
|
<div class="difficulty-skeleton loading-container">
|
||||||
<div class="item">
|
<div class="item">
|
||||||
|
@ -58,11 +58,11 @@
|
|||||||
display: block;
|
display: block;
|
||||||
@media (min-width: 485px) {
|
@media (min-width: 485px) {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
h5 {
|
h5 {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
.item {
|
.item {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
margin: 0px auto 20px;
|
margin: 0px auto 20px;
|
||||||
@ -131,7 +131,7 @@
|
|||||||
.latest-transactions {
|
.latest-transactions {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
table-layout:fixed;
|
table-layout:fixed;
|
||||||
tr, td, th {
|
tr, td, th {
|
||||||
border: 0px;
|
border: 0px;
|
||||||
}
|
}
|
||||||
@ -220,6 +220,11 @@
|
|||||||
.mempool-graph {
|
.mempool-graph {
|
||||||
height: 250px;
|
height: 250px;
|
||||||
}
|
}
|
||||||
|
.loadingGraphs{
|
||||||
|
height: 250px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.inc-tx-progress-bar {
|
.inc-tx-progress-bar {
|
||||||
max-width: 250px;
|
max-width: 250px;
|
||||||
@ -247,7 +252,7 @@
|
|||||||
color: #ffffff66;
|
color: #ffffff66;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
.item {
|
.item {
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
&:nth-child(1) {
|
&:nth-child(1) {
|
||||||
@ -276,25 +281,25 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@media (min-width: 376px) {
|
@media (min-width: 376px) {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
.item {
|
.item {
|
||||||
max-width: 150px;
|
max-width: 150px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: -webkit-fill-available;
|
width: -webkit-fill-available;
|
||||||
@media (min-width: 376px) {
|
@media (min-width: 376px) {
|
||||||
margin: 0 auto 0px;
|
margin: 0 auto 0px;
|
||||||
}
|
}
|
||||||
&:first-child{
|
&:first-child{
|
||||||
display: none;
|
display: none;
|
||||||
@media (min-width: 485px) {
|
@media (min-width: 485px) {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@media (min-width: 992px) {
|
@media (min-width: 992px) {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
@ -355,4 +360,4 @@
|
|||||||
.previous-retarget-sign {
|
.previous-retarget-sign {
|
||||||
margin-right: -2px;
|
margin-right: -2px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,11 @@ import { OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
|||||||
import { MempoolInfo, TransactionStripped } from '../interfaces/websocket.interface';
|
import { MempoolInfo, TransactionStripped } from '../interfaces/websocket.interface';
|
||||||
import { ApiService } from '../services/api.service';
|
import { ApiService } from '../services/api.service';
|
||||||
import { StateService } from '../services/state.service';
|
import { StateService } from '../services/state.service';
|
||||||
import * as Chartist from '@mempool/chartist';
|
|
||||||
import { formatDate } from '@angular/common';
|
import { formatDate } from '@angular/common';
|
||||||
import { WebsocketService } from '../services/websocket.service';
|
import { WebsocketService } from '../services/websocket.service';
|
||||||
import { SeoService } from '../services/seo.service';
|
import { SeoService } from '../services/seo.service';
|
||||||
import { StorageService } from '../services/storage.service';
|
import { StorageService } from '../services/storage.service';
|
||||||
|
import { EChartsOption } from 'echarts';
|
||||||
|
|
||||||
interface MempoolBlocksData {
|
interface MempoolBlocksData {
|
||||||
blocks: number;
|
blocks: number;
|
||||||
@ -34,7 +34,7 @@ interface MempoolInfoData {
|
|||||||
memPoolInfo: MempoolInfo;
|
memPoolInfo: MempoolInfo;
|
||||||
vBytesPerSecond: number;
|
vBytesPerSecond: number;
|
||||||
progressWidth: string;
|
progressWidth: string;
|
||||||
progressClass: string;
|
progressColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MempoolStatsData {
|
interface MempoolStatsData {
|
||||||
@ -74,15 +74,15 @@ export class DashboardComponent implements OnInit {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
|
||||||
this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$;
|
this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$;
|
||||||
this.seoService.resetTitle();
|
this.seoService.resetTitle();
|
||||||
this.websocketService.want(['blocks', 'stats', 'mempool-blocks', 'live-2h-chart']);
|
this.websocketService.want(['blocks', 'stats', 'mempool-blocks', 'live-2h-chart']);
|
||||||
this.network$ = merge(of(''), this.stateService.networkChanged$);
|
this.network$ = merge(of(''), this.stateService.networkChanged$);
|
||||||
this.collapseLevel = this.storageService.getValue('dashboard-collapsed') || 'one';
|
this.collapseLevel = this.storageService.getValue('dashboard-collapsed') || 'one';
|
||||||
this.mempoolLoadingStatus$ = this.stateService.loadingIndicators$.pipe(
|
this.mempoolLoadingStatus$ = this.stateService.loadingIndicators$
|
||||||
map((indicators) => indicators.mempool !== undefined ? indicators.mempool : 100)
|
.pipe(
|
||||||
);
|
map((indicators) => indicators.mempool !== undefined ? indicators.mempool : 100)
|
||||||
|
);
|
||||||
|
|
||||||
this.mempoolInfoData$ = combineLatest([
|
this.mempoolInfoData$ = combineLatest([
|
||||||
this.stateService.mempoolInfo$,
|
this.stateService.mempoolInfo$,
|
||||||
@ -92,11 +92,21 @@ export class DashboardComponent implements OnInit {
|
|||||||
map(([mempoolInfo, vbytesPerSecond]) => {
|
map(([mempoolInfo, vbytesPerSecond]) => {
|
||||||
const percent = Math.round((Math.min(vbytesPerSecond, this.vBytesPerSecondLimit) / this.vBytesPerSecondLimit) * 100);
|
const percent = Math.round((Math.min(vbytesPerSecond, this.vBytesPerSecondLimit) / this.vBytesPerSecondLimit) * 100);
|
||||||
|
|
||||||
let progressClass = 'bg-danger';
|
let progressColor = '#7CB342';
|
||||||
if (percent <= 75) {
|
if (vbytesPerSecond > 1667) {
|
||||||
progressClass = 'bg-success';
|
progressColor = '#FDD835';
|
||||||
} else if (percent <= 99) {
|
}
|
||||||
progressClass = 'bg-warning';
|
if (vbytesPerSecond > 2000) {
|
||||||
|
progressColor = '#FFB300';
|
||||||
|
}
|
||||||
|
if (vbytesPerSecond > 2500) {
|
||||||
|
progressColor = '#FB8C00';
|
||||||
|
}
|
||||||
|
if (vbytesPerSecond > 3000) {
|
||||||
|
progressColor = '#F4511E';
|
||||||
|
}
|
||||||
|
if (vbytesPerSecond > 3500) {
|
||||||
|
progressColor = '#D81B60';
|
||||||
}
|
}
|
||||||
|
|
||||||
const mempoolSizePercentage = (mempoolInfo.usage / mempoolInfo.maxmempool * 100);
|
const mempoolSizePercentage = (mempoolInfo.usage / mempoolInfo.maxmempool * 100);
|
||||||
@ -111,7 +121,7 @@ export class DashboardComponent implements OnInit {
|
|||||||
memPoolInfo: mempoolInfo,
|
memPoolInfo: mempoolInfo,
|
||||||
vBytesPerSecond: vbytesPerSecond,
|
vBytesPerSecond: vbytesPerSecond,
|
||||||
progressWidth: percent + '%',
|
progressWidth: percent + '%',
|
||||||
progressClass: progressClass,
|
progressColor: progressColor,
|
||||||
mempoolSizeProgress: mempoolSizeProgress,
|
mempoolSizeProgress: mempoolSizeProgress,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@ -164,7 +174,7 @@ export class DashboardComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let colorPreviousAdjustments = '#dc3545';
|
let colorPreviousAdjustments = '#dc3545';
|
||||||
if (previousRetarget){
|
if (previousRetarget) {
|
||||||
if (previousRetarget >= 0) {
|
if (previousRetarget >= 0) {
|
||||||
colorPreviousAdjustments = '#3bcc49';
|
colorPreviousAdjustments = '#3bcc49';
|
||||||
}
|
}
|
||||||
@ -191,7 +201,6 @@ export class DashboardComponent implements OnInit {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
this.mempoolBlocksData$ = this.stateService.mempoolBlocks$
|
this.mempoolBlocksData$ = this.stateService.mempoolBlocks$
|
||||||
.pipe(
|
.pipe(
|
||||||
map((mempoolBlocks) => {
|
map((mempoolBlocks) => {
|
||||||
@ -226,50 +235,32 @@ export class DashboardComponent implements OnInit {
|
|||||||
}, []),
|
}, []),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.mempoolStats$ = this.stateService.connectionState$.pipe(
|
this.mempoolStats$ = this.stateService.connectionState$
|
||||||
filter((state) => state === 2),
|
.pipe(
|
||||||
switchMap(() => this.apiService.list2HStatistics$()),
|
filter((state) => state === 2),
|
||||||
switchMap((mempoolStats) => {
|
switchMap(() => this.apiService.list2HStatistics$()),
|
||||||
return merge(
|
switchMap((mempoolStats) => {
|
||||||
this.stateService.live2Chart$
|
return merge(
|
||||||
.pipe(
|
this.stateService.live2Chart$
|
||||||
scan((acc, stats) => {
|
.pipe(
|
||||||
acc.unshift(stats);
|
scan((acc, stats) => {
|
||||||
acc = acc.slice(0, 120);
|
acc.unshift(stats);
|
||||||
return acc;
|
acc = acc.slice(0, 120);
|
||||||
}, mempoolStats)
|
return acc;
|
||||||
),
|
}, mempoolStats)
|
||||||
of(mempoolStats)
|
),
|
||||||
);
|
of(mempoolStats)
|
||||||
}),
|
);
|
||||||
map((mempoolStats) => {
|
}),
|
||||||
return {
|
map((mempoolStats) => {
|
||||||
mempool: mempoolStats,
|
const data = this.handleNewMempoolData(mempoolStats.concat([]));
|
||||||
weightPerSecond: this.handleNewMempoolData(mempoolStats.concat([])),
|
return {
|
||||||
};
|
mempool: mempoolStats,
|
||||||
}),
|
weightPerSecond: this.handleNewMempoolData(mempoolStats.concat([])),
|
||||||
share(),
|
};
|
||||||
);
|
}),
|
||||||
|
share(),
|
||||||
this.transactionsWeightPerSecondOptions = {
|
);
|
||||||
showArea: false,
|
|
||||||
showLine: true,
|
|
||||||
fullWidth: true,
|
|
||||||
showPoint: false,
|
|
||||||
low: 0,
|
|
||||||
axisY: {
|
|
||||||
offset: 40
|
|
||||||
},
|
|
||||||
axisX: {
|
|
||||||
labelInterpolationFnc: (value: any, index: any) => index % 24 === 0 ? formatDate(value, 'HH:mm', this.locale) : null,
|
|
||||||
offset: 20
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
Chartist.plugins.ctTargetLine({
|
|
||||||
value: 1667
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNewMempoolData(mempoolStats: OptimizedMempoolStats[]) {
|
handleNewMempoolData(mempoolStats: OptimizedMempoolStats[]) {
|
||||||
|
@ -235,7 +235,7 @@ body {
|
|||||||
color: #dc3545;
|
color: #dc3545;
|
||||||
}
|
}
|
||||||
|
|
||||||
.yellow-color {
|
.yellow-color {
|
||||||
color: #ffd800;
|
color: #ffd800;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,168 +255,64 @@ html:lang(ru) .card-title {
|
|||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Chartist */
|
/* MEMPOOL CHARTS */
|
||||||
$ct-series-names: (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z);
|
|
||||||
$ct-series-colors: (
|
|
||||||
#D81B60,
|
|
||||||
#8E24AA,
|
|
||||||
#5E35B1,
|
|
||||||
#3949AB,
|
|
||||||
#1E88E5,
|
|
||||||
#039BE5,
|
|
||||||
#00ACC1,
|
|
||||||
#00897B,
|
|
||||||
#43A047,
|
|
||||||
#7CB342,
|
|
||||||
#C0CA33,
|
|
||||||
#FDD835,
|
|
||||||
#FFB300,
|
|
||||||
#FB8C00,
|
|
||||||
#F4511E,
|
|
||||||
#6D4C41,
|
|
||||||
#757575,
|
|
||||||
#546E7A,
|
|
||||||
#b71c1c,
|
|
||||||
#880E4F,
|
|
||||||
#4A148C,
|
|
||||||
#311B92,
|
|
||||||
#1A237E,
|
|
||||||
#0D47A1,
|
|
||||||
#01579B,
|
|
||||||
#006064,
|
|
||||||
#004D40,
|
|
||||||
#1B5E20,
|
|
||||||
#33691E,
|
|
||||||
#827717,
|
|
||||||
#F57F17,
|
|
||||||
#FF6F00,
|
|
||||||
#E65100,
|
|
||||||
#BF360C,
|
|
||||||
#3E2723,
|
|
||||||
#212121,
|
|
||||||
#263238,
|
|
||||||
#a748ca,
|
|
||||||
#6188e2,
|
|
||||||
#a748ca,
|
|
||||||
#6188e2,
|
|
||||||
);
|
|
||||||
|
|
||||||
@import "../node_modules/@mempool/chartist/dist/scss/chartist.scss";
|
.mempool-wrapper-tooltip-chart {
|
||||||
|
height: 250px;
|
||||||
.ct-bar-label {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: bold;
|
|
||||||
fill: #fff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ct-target-line {
|
.echarts {
|
||||||
stroke: #f5f5f5;
|
height: 100%;
|
||||||
stroke-width: 3px;
|
min-height: 180px;
|
||||||
stroke-dasharray: 7px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ct-area {
|
.tx-wrapper-tooltip-chart, .fees-wrapper-tooltip-chart {
|
||||||
stroke: none;
|
display: flex;
|
||||||
fill-opacity: 0.9;
|
justify-content: space-between;
|
||||||
}
|
flex-direction: column;
|
||||||
|
background: rgba(#11131f, 0.85);
|
||||||
.ct-label {
|
color: #fff;
|
||||||
fill: rgba(255, 255, 255, 0.4);
|
padding: 10px 15px;
|
||||||
color: rgba(255, 255, 255, 0.4);
|
border-radius: 4px;
|
||||||
}
|
box-shadow: 1px 1px 10px rgba(0,0,0,0.2);
|
||||||
|
.item {
|
||||||
.ct-point-label {
|
text-align: left;
|
||||||
fill: rgba(255, 255, 255, 1);
|
display: flex;
|
||||||
color: rgba(255, 255, 255, 1);
|
.indicator {
|
||||||
font-size: 14px;
|
display: block;
|
||||||
}
|
margin-right: 5px;
|
||||||
|
border-radius: 10px;
|
||||||
.ct-grid {
|
margin-top: 5px;
|
||||||
stroke: rgba(255, 255, 255, 0.2);
|
width: 9px;
|
||||||
}
|
height: 9px;
|
||||||
|
|
||||||
/* LEGEND */
|
|
||||||
|
|
||||||
.ct-legend {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 10;
|
|
||||||
left: 0px;
|
|
||||||
list-style: none;
|
|
||||||
font-size: 13px;
|
|
||||||
padding: 0px 0px 0px 30px;
|
|
||||||
top: 90px;
|
|
||||||
|
|
||||||
li {
|
|
||||||
position: relative;
|
|
||||||
padding-left: 23px;
|
|
||||||
margin-bottom: 0px;
|
|
||||||
}
|
}
|
||||||
|
.value {
|
||||||
li:before {
|
text-align: right;
|
||||||
width: 12px;
|
span {
|
||||||
height: 12px;
|
color: #212121 !important;
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: 3px;
|
|
||||||
content: '';
|
|
||||||
border: 3px solid transparent;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
li.inactive:before {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.ct-legend-inside {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@for $i from 0 to length($ct-series-colors) {
|
|
||||||
.ct-series-#{$i}:before {
|
|
||||||
background-color: nth($ct-series-colors, $i + 1);
|
|
||||||
border-color: nth($ct-series-colors, $i + 1);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grow {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.chartist-tooltip {
|
.fees-wrapper-tooltip-chart {
|
||||||
position: absolute;
|
.item {
|
||||||
display: inline-block;
|
font-size: 9px;
|
||||||
opacity: 0;
|
line-height: 1;
|
||||||
min-width: 5em;
|
}
|
||||||
padding: .5em;
|
.indicator {
|
||||||
background: #F4C63D;
|
margin-right: 5px !important;
|
||||||
color: #453D3F;
|
border-radius: 10px !important;
|
||||||
font-family: Oxygen,Helvetica,Arial,sans-serif;
|
margin-top: 0px !important;
|
||||||
font-weight: 700;
|
}
|
||||||
text-align: center;
|
}
|
||||||
pointer-events: none;
|
|
||||||
z-index: 1;
|
|
||||||
-webkit-transition: opacity .2s linear;
|
|
||||||
-moz-transition: opacity .2s linear;
|
|
||||||
-o-transition: opacity .2s linear;
|
|
||||||
transition: opacity .2s linear; }
|
|
||||||
.chartist-tooltip:before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
left: 50%;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
margin-left: -15px;
|
|
||||||
border: 15px solid transparent;
|
|
||||||
border-top-color: #F4C63D; }
|
|
||||||
.chartist-tooltip.tooltip-show {
|
|
||||||
opacity: 1; }
|
|
||||||
|
|
||||||
.ct-area, .ct-line {
|
.fee-distribution-chart {
|
||||||
pointer-events: none; }
|
height: 250px;
|
||||||
|
|
||||||
.ct-bar {
|
|
||||||
stroke-width: 1px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
@ -639,7 +535,7 @@ th {
|
|||||||
.card {
|
.card {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
display: block;
|
display: block;
|
||||||
@ -653,17 +549,17 @@ th {
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapsed{
|
.collapsed{
|
||||||
background-color: #2d3348;
|
background-color: #2d3348;
|
||||||
color: #1bd8f4;
|
color: #1bd8f4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.subtitle {
|
.subtitle {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
@ -675,7 +571,6 @@ th {
|
|||||||
|
|
||||||
|
|
||||||
.pagination-container {
|
.pagination-container {
|
||||||
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@ -698,4 +593,4 @@ th {
|
|||||||
.tooltip.show {
|
.tooltip.show {
|
||||||
width: 220px;
|
width: 220px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user