diff --git a/frontend/cypress/integration/mainnet/mainnet.spec.ts b/frontend/cypress/integration/mainnet/mainnet.spec.ts
index 7e56c7e97..5839f4f51 100644
--- a/frontend/cypress/integration/mainnet/mainnet.spec.ts
+++ b/frontend/cypress/integration/mainnet/mainnet.spec.ts
@@ -281,12 +281,12 @@ describe('Mainnet', () => {
});
});
- it('loads the tv screen - mobile', () => {
+ it.only('loads the tv screen - mobile', () => {
cy.viewport('iphone-6');
cy.visit('/tv');
cy.waitForSkeletonGone();
cy.get('.chart-holder');
- cy.get('.blockchain-wrapper').should('be.visible');
+ cy.get('.blockchain-wrapper').should('not.visible');
});
it('loads the api screen', () => {
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 438f38aff..e27b73b20 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -23,7 +23,7 @@
"@fortawesome/fontawesome-common-types": "^0.2.35",
"@fortawesome/fontawesome-svg-core": "^1.2.35",
"@fortawesome/free-solid-svg-icons": "^5.15.3",
- "@mempool/chartist": "^0.11.4",
+ "@juggle/resize-observer": "^3.3.1",
"@mempool/mempool.js": "^2.2.4",
"@ng-bootstrap/ng-bootstrap": "^7.0.0",
"@nguniversal/express-engine": "11.2.1",
@@ -32,9 +32,11 @@
"browserify": "^17.0.0",
"clipboard": "^2.0.4",
"domino": "^2.1.6",
+ "echarts": "^5.1.2",
"express": "^4.17.1",
"lightweight-charts": "^3.3.0",
"ngx-bootrap-multiselect": "^2.0.0",
+ "ngx-echarts": "^7.0.1",
"ngx-infinite-scroll": "^10.0.1",
"qrcode": "^1.4.4",
"rxjs": "^6.6.7",
@@ -2252,13 +2254,10 @@
"schema-utils": "^2.7.0"
}
},
- "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/@juggle/resize-observer": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.3.1.tgz",
+ "integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw=="
},
"node_modules/@mempool/mempool.js": {
"version": "2.2.4",
@@ -6931,6 +6930,20 @@
"safer-buffer": "^2.1.0"
}
},
+ "node_modules/echarts": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.1.2.tgz",
+ "integrity": "sha512-okUhO4sw22vwZp+rTPNjd/bvTdpug4K4sHNHyrV8NdAncIX9/AarlolFqtJCAYKGFYhUBNjIWu1EznFrSWTFxg==",
+ "dependencies": {
+ "tslib": "2.0.3",
+ "zrender": "5.1.1"
+ }
+ },
+ "node_modules/echarts/node_modules/tslib": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
+ "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
+ },
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -11916,6 +11929,18 @@
"tslib": "^2.0.0"
}
},
+ "node_modules/ngx-echarts": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-7.0.1.tgz",
+ "integrity": "sha512-DDRwEssQRjjgPElEjF1emORoUEnF6OMZ20xnQBLueSHitM7XnIUErYVe9GMmm/jCtI+iPvJPEedyxMPl62nHLw==",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@juggle/resize-observer": ">=3.3.1",
+ "echarts": ">=5.0.0"
+ }
+ },
"node_modules/ngx-infinite-scroll": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/ngx-infinite-scroll/-/ngx-infinite-scroll-10.0.1.tgz",
@@ -19926,6 +19951,19 @@
"dependencies": {
"tslib": "^2.0.0"
}
+ },
+ "node_modules/zrender": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.1.1.tgz",
+ "integrity": "sha512-oeWlmUZPQdS9f5hK4pV21tHPqA3wgQ7CkKkw7l0CCBgWlJ/FP+lRgLFtUBW6yam4JX8y9CdHJo1o587VVrbcoQ==",
+ "dependencies": {
+ "tslib": "2.0.3"
+ }
+ },
+ "node_modules/zrender/node_modules/tslib": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
+ "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
}
},
"dependencies": {
@@ -21914,10 +21952,10 @@
"schema-utils": "^2.7.0"
}
},
- "@mempool/chartist": {
- "version": "0.11.4",
- "resolved": "https://registry.npmjs.org/@mempool/chartist/-/chartist-0.11.4.tgz",
- "integrity": "sha512-wSemsw2NIWS7/SHxjDe9upSdUETxNRebY0ByaJzcONKUzJSUzMuSNmKEdD3kr/g02H++JvsXR2znLC6tYEAbPA=="
+ "@juggle/resize-observer": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.3.1.tgz",
+ "integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw=="
},
"@mempool/mempool.js": {
"version": "2.2.4",
@@ -25975,6 +26013,22 @@
"safer-buffer": "^2.1.0"
}
},
+ "echarts": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.1.2.tgz",
+ "integrity": "sha512-okUhO4sw22vwZp+rTPNjd/bvTdpug4K4sHNHyrV8NdAncIX9/AarlolFqtJCAYKGFYhUBNjIWu1EznFrSWTFxg==",
+ "requires": {
+ "tslib": "2.0.3",
+ "zrender": "5.1.1"
+ },
+ "dependencies": {
+ "tslib": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
+ "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
+ }
+ }
+ },
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -30136,6 +30190,14 @@
"tslib": "^2.0.0"
}
},
+ "ngx-echarts": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-7.0.1.tgz",
+ "integrity": "sha512-DDRwEssQRjjgPElEjF1emORoUEnF6OMZ20xnQBLueSHitM7XnIUErYVe9GMmm/jCtI+iPvJPEedyxMPl62nHLw==",
+ "requires": {
+ "tslib": "^2.0.0"
+ }
+ },
"ngx-infinite-scroll": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/ngx-infinite-scroll/-/ngx-infinite-scroll-10.0.1.tgz",
@@ -36780,6 +36842,21 @@
"requires": {
"tslib": "^2.0.0"
}
+ },
+ "zrender": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.1.1.tgz",
+ "integrity": "sha512-oeWlmUZPQdS9f5hK4pV21tHPqA3wgQ7CkKkw7l0CCBgWlJ/FP+lRgLFtUBW6yam4JX8y9CdHJo1o587VVrbcoQ==",
+ "requires": {
+ "tslib": "2.0.3"
+ },
+ "dependencies": {
+ "tslib": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
+ "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
+ }
+ }
}
}
}
diff --git a/frontend/package.json b/frontend/package.json
index 73e93b4d7..5cf7e0c95 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -67,7 +67,7 @@
"@fortawesome/fontawesome-common-types": "^0.2.35",
"@fortawesome/fontawesome-svg-core": "^1.2.35",
"@fortawesome/free-solid-svg-icons": "^5.15.3",
- "@mempool/chartist": "^0.11.4",
+ "@juggle/resize-observer": "^3.3.1",
"@mempool/mempool.js": "^2.2.4",
"@ng-bootstrap/ng-bootstrap": "^7.0.0",
"@nguniversal/express-engine": "11.2.1",
@@ -76,9 +76,11 @@
"browserify": "^17.0.0",
"clipboard": "^2.0.4",
"domino": "^2.1.6",
+ "echarts": "^5.1.2",
"express": "^4.17.1",
"lightweight-charts": "^3.3.0",
"ngx-bootrap-multiselect": "^2.0.0",
+ "ngx-echarts": "^7.0.1",
"ngx-infinite-scroll": "^10.0.1",
"qrcode": "^1.4.4",
"rxjs": "^6.6.7",
diff --git a/frontend/src/app/app.constants.ts b/frontend/src/app/app.constants.ts
index 74d4bf780..7197c37df 100644
--- a/frontend/src/app/app.constants.ts
+++ b/frontend/src/app/app.constants.ts
@@ -31,6 +31,46 @@ export const mempoolFeeColors = [
'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,
250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000];
diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts
index b7719ebcf..d263db3ad 100644
--- a/frontend/src/app/app.module.ts
+++ b/frontend/src/app/app.module.ts
@@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
+import { NgxEchartsModule } from 'ngx-echarts';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './components/app/app.component';
@@ -26,13 +27,13 @@ import { LiquidMasterPageComponent } from './components/liquid-master-page/liqui
import { AboutComponent } from './components/about/about.component';
import { TelevisionComponent } from './components/television/television.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 { BlockchainComponent } from './components/blockchain/blockchain.component';
import { FooterComponent } from './components/footer/footer.component';
import { AudioService } from './services/audio.service';
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 { TimeSpanComponent } from './components/time-span/time-span.component';
import { SeoService } from './services/seo.service';
import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph.component';
@@ -78,10 +79,10 @@ import { SponsorComponent } from './components/sponsor/sponsor.component';
TimeSpanComponent,
AddressLabelsComponent,
MempoolBlocksComponent,
- ChartistComponent,
FooterComponent,
MempoolBlockComponent,
FeeDistributionGraphComponent,
+ IncomingTransactionsGraphComponent,
MempoolGraphComponent,
AssetComponent,
AssetsComponent,
@@ -106,6 +107,9 @@ import { SponsorComponent } from './components/sponsor/sponsor.component';
NgbTypeaheadModule,
FontAwesomeModule,
SharedModule,
+ NgxEchartsModule.forRoot({
+ echarts: () => import('echarts')
+ })
],
providers: [
ElectrsApiService,
diff --git a/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.html b/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.html
index 2fba6ec90..3465bde35 100644
--- a/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.html
+++ b/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.html
@@ -1,9 +1,5 @@
-
-
-
+
diff --git a/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.scss b/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.scss
deleted file mode 100644
index e69de29bb..000000000
diff --git a/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.ts b/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.ts
index 0d6ed9c74..8c90036fd 100644
--- a/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.ts
+++ b/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.ts
@@ -1,70 +1,83 @@
-import { Component, Input, OnChanges, ChangeDetectionStrategy } from '@angular/core';
-import * as Chartist from '@mempool/chartist';
+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',
- styleUrls: ['./fee-distribution-graph.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class FeeDistributionGraphComponent implements OnChanges {
- @Input() feeRange;
+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;
- mempoolVsizeFeesData: any;
mempoolVsizeFeesOptions: any;
+ mempoolVsizeFeesInitOptions = {
+ renderer: 'svg'
+ };
- 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];
+ constructor() { }
- constructor(
- ) { }
-
- 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)
- };
+ 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,
+ }
+ }]
+ };
+ }
}
diff --git a/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.html b/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.html
new file mode 100644
index 000000000..537aaceab
--- /dev/null
+++ b/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.html
@@ -0,0 +1 @@
+
diff --git a/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts b/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts
new file mode 100644
index 000000000..68d1744e3
--- /dev/null
+++ b/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts
@@ -0,0 +1,180 @@
+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';
+import { StorageService } from 'src/app/services/storage.service';
+
+@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 = '0';
+ @Input() template: ('widget' | 'advanced') = 'widget';
+
+ mempoolStatsChartOption: EChartsOption = {};
+ mempoolStatsChartInitOption = {
+ renderer: 'svg'
+ };
+ windowPreference: string;
+
+ constructor(
+ @Inject(LOCALE_ID) private locale: string,
+ private storageService: StorageService,
+ ) { }
+
+ ngOnChanges(): void {
+ this.windowPreference = this.storageService.getValue('graphWindowPreference');
+ this.mountChart();
+ }
+
+ ngOnInit(): void {
+ this.mountChart();
+ }
+
+ mountChart(): void {
+ this.mempoolStatsChartOption = {
+ grid: {
+ height: this.height,
+ right: this.right,
+ top: this.top,
+ left: this.left,
+ },
+ animation: false,
+ dataZoom: [{
+ type: 'inside',
+ realtime: true,
+ zoomOnMouseWheel: (this.template === 'advanced') ? true : false,
+ maxSpan: 100,
+ minSpan: 10,
+ }, {
+ show: (this.template === 'advanced') ? true : false,
+ type: 'slider',
+ brushSelect: false,
+ realtime: true,
+ selectedDataBackground: {
+ lineStyle: {
+ color: '#fff',
+ opacity: 0.45,
+ },
+ areaStyle: {
+ opacity: 0,
+ }
+ }
+ }],
+ 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: `width: ${(['2h', '24h'].includes(this.windowPreference) || this.template === 'widget') ? '125px' : '135px'};
+ background: transparent;
+ border: none;
+ box-shadow: none;`,
+ axisPointer: {
+ type: 'line',
+ },
+ formatter: (params: any) => {
+ const colorSpan = (color: string) => ``;
+ let itemFormatted = '' + params[0].axisValue + '
';
+ params.map((item: any, index: number) => {
+ if (index < 26) {
+ itemFormatted += `
+
${colorSpan(item.color)}
+
+
${item.value} vB/s
+
`;
+ }
+ });
+ return `${itemFormatted}
`;
+ }
+ },
+ xAxis: {
+ type: 'category',
+ data: this.data.labels.map((value: any) => `${formatDate(value, 'M/d', this.locale)}\n${formatDate(value, 'H:mm', this.locale)}`),
+ },
+ yAxis: {
+ type: 'value',
+ splitLine: {
+ lineStyle: {
+ type: 'dotted',
+ color: '#ffffff66',
+ opacity: 0.25,
+ }
+ }
+ },
+ series: [
+ {
+ data: this.data.series[0],
+ type: 'line',
+ smooth: (this.template === 'advanced') ? false : 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'
+ }
+ },
+ };
+ }
+}
diff --git a/frontend/src/app/components/mempool-block/mempool-block.component.html b/frontend/src/app/components/mempool-block/mempool-block.component.html
index 744058614..2d72ff730 100644
--- a/frontend/src/app/components/mempool-block/mempool-block.component.html
+++ b/frontend/src/app/components/mempool-block/mempool-block.component.html
@@ -9,7 +9,7 @@
-
diff --git a/frontend/src/app/components/mempool-block/mempool-block.component.scss b/frontend/src/app/components/mempool-block/mempool-block.component.scss
index 4db5e0b10..f40515844 100644
--- a/frontend/src/app/components/mempool-block/mempool-block.component.scss
+++ b/frontend/src/app/components/mempool-block/mempool-block.component.scss
@@ -13,11 +13,8 @@
.fiat {
font-size: 13px;
- display: block;
- @media (min-width: 992px) {
- display: inline-block;
- margin-left: 10px;
- }
+ display: inline-block;
+ margin-left: 10px;
}
.table {
@@ -38,4 +35,11 @@ h1 {
float: left;
margin-right: 10px;
}
-}
\ No newline at end of file
+}
+
+.chart-container{
+ margin: 20px auto;
+ @media (min-width: 768px) {
+ margin: auto;
+ }
+}
diff --git a/frontend/src/app/components/mempool-graph/mempool-graph.component.html b/frontend/src/app/components/mempool-graph/mempool-graph.component.html
index ff91f4a90..ac67c6e9e 100644
--- a/frontend/src/app/components/mempool-graph/mempool-graph.component.html
+++ b/frontend/src/app/components/mempool-graph/mempool-graph.component.html
@@ -1,6 +1 @@
-
-
+
diff --git a/frontend/src/app/components/mempool-graph/mempool-graph.component.ts b/frontend/src/app/components/mempool-graph/mempool-graph.component.ts
index b227c9884..3cb6fff4e 100644
--- a/frontend/src/app/components/mempool-graph/mempool-graph.component.ts
+++ b/frontend/src/app/components/mempool-graph/mempool-graph.component.ts
@@ -1,10 +1,11 @@
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 * as Chartist from '@mempool/chartist';
import { OptimizedMempoolStats } from 'src/app/interfaces/node-api.interface';
import { StateService } from 'src/app/services/state.service';
import { StorageService } from 'src/app/services/storage.service';
+import { EChartsOption } from 'echarts';
+import { feeLevels, chartColors } from 'src/app/app.constants';
@Component({
selector: 'app-mempool-graph',
@@ -12,134 +13,299 @@ import { StorageService } from 'src/app/services/storage.service';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MempoolGraphComponent implements OnInit, OnChanges {
- @Input() data;
- @Input() dateSpan = '2h';
- @Input() showLegend = true;
- @Input() offsetX = 40;
- @Input() small = false;
+ @Input() data: any[];
+ @Input() limitFee = 350;
+ @Input() height: number | string = 200;
+ @Input() top: number | string = 20;
+ @Input() right: number | string = 10;
+ @Input() left: number | string = 75;
+ @Input() template: ('widget' | 'advanced') = 'widget';
+ @Input() showZoom = true;
- mempoolVsizeFeesOptions: any;
mempoolVsizeFeesData: any;
-
- isMobile = window.innerWidth <= 767.98;
- inverted: boolean;
+ mempoolVsizeFeesOptions: EChartsOption;
+ mempoolVsizeFeesInitOptions = {
+ renderer: 'svg',
+ };
+ windowPreference: string;
+ hoverIndexSerie = 0;
+ feeLimitIndex: number;
+ feeLevelsOrdered = [];
constructor(
private vbytesPipe: VbytesPipe,
private stateService: StateService,
- @Inject(LOCALE_ID) private locale: string,
private storageService: StorageService,
+ @Inject(LOCALE_ID) private locale: string,
) { }
ngOnInit(): void {
- let labelHops = !this.showLegend ? 48 : 24;
- 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);
+ this.feeLevelsOrdered = feeLevels.map((sat, i, arr) => {
+ if (arr[i] === this.limitFee) { this.feeLimitIndex = i; }
+ if (arr[i] < this.limitFee) {
+ if (i === 0) { return '0 - 1'; }
+ return `${arr[i - 1]} - ${arr[i]}`;
+ } else {
+ return `${this.limitFee}+`;
}
- 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 })
- );
- }
+ });
+ this.mountFeeChart();
}
ngOnChanges() {
- this.inverted = this.storageService.getValue('inverted-graph') === 'true';
+ this.windowPreference = this.storageService.getValue('graphWindowPreference');
this.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([]));
+ this.mountFeeChart();
+ }
+
+ onChartReady(myChart: any) {
+ myChart.on('mouseover', 'series', (serie: any) => {
+ this.hoverIndexSerie = serie.seriesIndex;
+ });
}
handleNewMempoolData(mempoolStats: OptimizedMempoolStats[]) {
mempoolStats.reverse();
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
if (this.stateService.network !== 'liquid') {
- finalArrayVbyte.shift();
+ finalArrayVByte.shift();
}
return {
labels: labels,
- series: finalArrayVbyte
+ series: finalArrayVByte
};
}
generateArray(mempoolStats: OptimizedMempoolStats[]) {
const finalArray: number[][] = [];
let feesArray: number[] = [];
-
for (let index = 37; index > -1; index--) {
feesArray = [];
mempoolStats.forEach((stats) => {
- const theFee = stats.vsizes[index].toString();
- if (theFee) {
- feesArray.push(parseInt(theFee, 10));
- } else {
- feesArray.push(0);
- }
+ feesArray.push(stats.vsizes[index] ? stats.vsizes[index] : 0);
});
- if (this.inverted && finalArray.length) {
- feesArray = feesArray.map((value, i) => value + finalArray[finalArray.length - 1][i]);
- }
finalArray.push(feesArray);
}
finalArray.reverse();
return finalArray;
}
+
+ mountFeeChart() {
+ const { labels, series } = this.mempoolVsizeFeesData;
+
+ const seriesGraph = series.map((value: Array
, index: number) => {
+ if (index <= this.feeLimitIndex){
+ return {
+ name: this.feeLevelsOrdered[index],
+ type: 'line',
+ stack: 'total',
+ smooth: false,
+ markPoint: {
+ symbol: 'rect',
+ },
+ lineStyle: {
+ width: 0,
+ opacity: 0,
+ },
+ symbolSize: (this.template === 'advanced') ? 10 : 10,
+ showSymbol: false,
+ areaStyle: {
+ opacity: 1,
+ color: chartColors[index],
+ },
+ emphasis: {
+ focus: 'series',
+ areaStyle: {
+ opacity: 1,
+ },
+ itemStyle: {
+ opacity: 0.2,
+ },
+ },
+ itemStyle: {
+ opacity: 0,
+ },
+ data: this.vbytesPipe.transform(value, 2, 'vB', 'MvB', true)
+ };
+ }
+ });
+
+ this.mempoolVsizeFeesOptions = {
+ emphasis: {
+ areaStyle: {
+ opacity: 1,
+ }
+ },
+ color: chartColors,
+ tooltip: {
+ show: true,
+ trigger: 'axis',
+ alwaysShowContent: false,
+ position: (pos, params, el, elRect, size) => {
+ const positions = { top: (this.template === 'advanced') ? 30 : -30 };
+ positions[['left', 'right'][+(pos[0] < size.viewSize[0] / 2)]] = 60;
+ return positions;
+ },
+ extraCssText: `width: ${(this.template === 'advanced') ? '275px' : '200px'};
+ background: transparent;
+ border: none;
+ box-shadow: none;`,
+ axisPointer: {
+ type: 'line',
+ },
+ formatter: (params: any) => {
+ const legendName = (index: number) => this.feeLevelsOrdered[index];
+ const colorSpan = (index: number) => `
+
+ ${legendName(index)}
+ `;
+ const totals = (values: any) => {
+ let totalValueTemp = 0;
+ const totalValueArrayTemp = [];
+ const valuesInverted = values.slice(0).reverse();
+ for (const item of valuesInverted) {
+ totalValueTemp += item.value;
+ totalValueArrayTemp.push(totalValueTemp);
+ }
+ return {
+ totalValue: totalValueTemp,
+ totalValueArray: totalValueArrayTemp.reverse()
+ };
+ };
+ const { totalValue, totalValueArray } = totals(params);
+ const title = `
+ ${params[0].axisValue}
+
+ ${this.vbytesPipe.transform(totalValue, 2, 'vB', 'MvB', false)}
+
+
`;
+ const itemFormatted = [];
+ let totalParcial = 0;
+ let progressPercentageText = '';
+ params.map((item: any, index: number) => {
+ totalParcial += item.value;
+ let progressPercentage = 0;
+ let progressPercentageSum = 0;
+ if (index <= this.feeLimitIndex) {
+ progressPercentage = (item.value / totalValue) * 100;
+ progressPercentageSum = (totalValueArray[index] / totalValue) * 100;
+ let activeItemClass = '';
+ if (this.hoverIndexSerie === index) {
+ progressPercentageText = `
+
+ ${progressPercentage.toFixed(2)}
+ %
+
+
+ ${this.vbytesPipe.transform(totalParcial, 2, 'vB', 'MvB', false)}
+
+
+
+
+
+
+
`;
+ activeItemClass = 'active';
+ }
+ itemFormatted.push(`
+
+ ${colorSpan(index)}
+ |
+
+ ${this.vbytesPipe.transform(item.value, 2, 'vB', 'MvB', false)}
+ |
+
+
+ ${this.vbytesPipe.transform(totalValueArray[index], 2, 'vB', 'MvB', false)}
+
+ |
+
+
+
+
+ |
+
`);
+ }
+ });
+ const classActive = (this.template === 'advanced') ? 'fees-wrapper-tooltip-chart-advanced' : '';
+ return ``;
+ }
+ },
+ dataZoom: [{
+ type: 'inside',
+ realtime: true,
+ zoomOnMouseWheel: (this.template === 'advanced') ? true : false,
+ maxSpan: 100,
+ minSpan: 10,
+ }, {
+ show: (this.template === 'advanced' && this.showZoom) ? true : false,
+ type: 'slider',
+ brushSelect: false,
+ realtime: true,
+ bottom: 0,
+ selectedDataBackground: {
+ lineStyle: {
+ color: '#fff',
+ opacity: 0.45,
+ },
+ areaStyle: {
+ opacity: 0,
+ }
+ }
+ }],
+ animation: false,
+ grid: {
+ height: this.height,
+ right: this.right,
+ top: this.top,
+ left: this.left,
+ },
+ xAxis: [
+ {
+ type: 'category',
+ boundaryGap: false,
+ axisLine: { onZero: false },
+ data: labels.map((value: any) => `${formatDate(value, 'M/d', this.locale)}\n${formatDate(value, 'H:mm', this.locale)}`),
+ }
+ ],
+ yAxis: {
+ type: 'value',
+ axisLine: { onZero: false },
+ axisLabel: {
+ formatter: (value: number) => (`${this.vbytesPipe.transform(value, 2, 'vB', 'MvB', true)}`),
+ },
+ splitLine: {
+ lineStyle: {
+ type: 'dotted',
+ color: '#ffffff66',
+ opacity: 0.25,
+ }
+ }
+ },
+ series: seriesGraph
+ };
+ }
}
+
diff --git a/frontend/src/app/components/statistics/chartist.component.scss b/frontend/src/app/components/statistics/chartist.component.scss
deleted file mode 100644
index 1f72f6bb4..000000000
--- a/frontend/src/app/components/statistics/chartist.component.scss
+++ /dev/null
@@ -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;
- }
\ No newline at end of file
diff --git a/frontend/src/app/components/statistics/chartist.component.ts b/frontend/src/app/components/statistics/chartist.component.ts
deleted file mode 100644
index d87c464f7..000000000
--- a/frontend/src/app/components/statistics/chartist.component.ts
+++ /dev/null
@@ -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: '',
- styleUrls: ['./chartist.component.scss'],
- encapsulation: ViewEncapsulation.None,
-})
-export class ChartistComponent implements OnInit, OnChanges, OnDestroy {
- @Input()
- // @ts-ignore
- public data: Promise | Chartist.IChartistData;
-
- // @ts-ignore
- @Input() public type: Promise | ChartType;
-
- @Input()
- // @ts-ignore
- public options: Promise | Chartist.IChartOptions;
-
- @Input()
- // @ts-ignore
- public responsiveOptions: Promise | 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 {
- 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 {
- 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 = '' + meta + '';
-
- if (hasMeta) {
- tooltipText += meta + '
';
- } 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) + '
';
- }
- }
- }
-
- 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 = '' + value + '';
- 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;
-}
diff --git a/frontend/src/app/components/statistics/statistics.component.html b/frontend/src/app/components/statistics/statistics.component.html
index 8aaf5d833..c06ef4b37 100644
--- a/frontend/src/app/components/statistics/statistics.component.html
+++ b/frontend/src/app/components/statistics/statistics.component.html
@@ -36,29 +36,36 @@
1Y
-
-
+
diff --git a/frontend/src/app/components/statistics/statistics.component.scss b/frontend/src/app/components/statistics/statistics.component.scss
index 04f8734bd..31eecb8c3 100644
--- a/frontend/src/app/components/statistics/statistics.component.scss
+++ b/frontend/src/app/components/statistics/statistics.component.scss
@@ -56,4 +56,8 @@
text-align: center;
height: 80vh;
justify-content: center;
-}
\ No newline at end of file
+}
+
+.incoming-transactions-graph {
+ height: 600px;
+}
diff --git a/frontend/src/app/components/statistics/statistics.component.ts b/frontend/src/app/components/statistics/statistics.component.ts
index 39350c247..c7f0042d9 100644
--- a/frontend/src/app/components/statistics/statistics.component.ts
+++ b/frontend/src/app/components/statistics/statistics.component.ts
@@ -1,6 +1,5 @@
import { Component, OnInit, LOCALE_ID, Inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
-import { formatDate } from '@angular/common';
import { FormGroup, FormBuilder } from '@angular/forms';
import { of, merge} from 'rxjs';
import { switchMap } from 'rxjs/operators';
@@ -9,7 +8,6 @@ import { OptimizedMempoolStats } from '../../interfaces/node-api.interface';
import { WebsocketService } from '../../services/websocket.service';
import { ApiService } from '../../services/api.service';
-import * as Chartist from '@mempool/chartist';
import { StateService } from 'src/app/services/state.service';
import { SeoService } from 'src/app/services/seo.service';
import { StorageService } from 'src/app/services/storage.service';
@@ -31,10 +29,7 @@ export class StatisticsComponent implements OnInit {
mempoolUnconfirmedTransactionsData: any;
mempoolTransactionsWeightPerSecondData: any;
- transactionsWeightPerSecondOptions: any;
-
radioGroupForm: FormGroup;
- inverted: boolean;
graphWindowPreference: String;
constructor(
@@ -51,7 +46,6 @@ export class StatisticsComponent implements OnInit {
ngOnInit() {
this.seoService.setTitle($localize`:@@5d4f792f048fcaa6df5948575d7cb325c9393383:Graphs`);
this.stateService.networkChanged$.subscribe((network) => this.network = network);
- this.inverted = this.storageService.getValue('inverted-graph') === 'true';
this.graphWindowPreference = this.storageService.getValue('graphWindowPreference') ? this.storageService.getValue('graphWindowPreference').trim() : '2h';
const isMobile = window.innerWidth <= 767.98;
let labelHops = isMobile ? 48 : 24;
@@ -64,43 +58,6 @@ export class StatisticsComponent implements OnInit {
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
.fragment
.subscribe((fragment) => {
@@ -164,11 +121,6 @@ export class StatisticsComponent implements OnInit {
};
}
- invertGraph() {
- this.storageService.setValue('inverted-graph', !this.inverted);
- document.location.reload();
- }
-
saveGraphPreference() {
this.storageService.setValue('graphWindowPreference', this.radioGroupForm.controls.dateSpan.value);
}
diff --git a/frontend/src/app/components/television/television.component.html b/frontend/src/app/components/television/television.component.html
index e8a423b0b..e5134c9bd 100644
--- a/frontend/src/app/components/television/television.component.html
+++ b/frontend/src/app/components/television/television.component.html
@@ -4,12 +4,17 @@
-
-
-
-
+
+
-
diff --git a/frontend/src/app/components/television/television.component.scss b/frontend/src/app/components/television/television.component.scss
index f45908655..78e27750c 100644
--- a/frontend/src/app/components/television/television.component.scss
+++ b/frontend/src/app/components/television/television.component.scss
@@ -16,40 +16,24 @@
}
.chart-holder {
- height: calc(100vh - 270px);
- min-height: 525px;
- padding-left: 20px;
- width: 98.5%;
- padding-top: 20px;
- @media(min-width: 992px){
- padding-top: 10px;
- }
- @media(min-height: 800px){
- padding-top: 60px !important;
- }
+ height: 650px;
+ width: 100%;
+ margin: 30px auto 0;
}
.blockchain-wrapper {
-
- display: flex;
+ display: block;
height: 100%;
min-height: 240px;
position: relative;
- top: -20px;
- @media(min-height: 800px) {
- top: 10px;
- }
-
+ top: 30px;
+
.position-container {
position: absolute;
left: 50%;
bottom: 170px;
}
- .chart-holder {
- height: calc(100% - 220px);
- }
-
#divider {
width: 3px;
height: 175px;
@@ -64,29 +48,9 @@
top: -28px;
}
}
-
- @media (min-width: 1920px) {
- .position-container {
- transform: scale(1.3);
- bottom: 210px;
- }
- .chart-holder {
- height: calc(100% - 280px);
- }
- }
-
}
-:host ::ng-deep .ct-legend {
- top: 20px !important;
- display: flex;
- flex-direction: column-reverse;
- @media(min-height: 800px){
- padding-top: 40px !important;
- }
-}
-
.tv-container {
display: flex;
margin-top: 0px;
flex-direction: column;
-}
\ No newline at end of file
+}
diff --git a/frontend/src/app/dashboard/dashboard.component.html b/frontend/src/app/dashboard/dashboard.component.html
index 47990f639..cbda3076a 100644
--- a/frontend/src/app/dashboard/dashboard.component.html
+++ b/frontend/src/app/dashboard/dashboard.component.html
@@ -47,8 +47,12 @@
-
@@ -59,12 +63,11 @@
-
@@ -197,13 +200,14 @@
-
+
{{ mempoolInfoData.value.vBytesPerSecond | ceil | number }} vB/s
+
Difficulty Adjustment
@@ -228,11 +232,11 @@
- {{ epochData.change | absolute | number: '1.2-2' }}
+ {{ epochData.change | absolute | number: '1.2-2' }}
%
-
Previous:
+
Previous:
0; else arrowDownPreviousDifficulty" >
@@ -257,6 +261,12 @@
+
+
+
+
diff --git a/frontend/src/app/dashboard/dashboard.component.scss b/frontend/src/app/dashboard/dashboard.component.scss
index 37a37d529..a3877e855 100644
--- a/frontend/src/app/dashboard/dashboard.component.scss
+++ b/frontend/src/app/dashboard/dashboard.component.scss
@@ -58,11 +58,11 @@
display: block;
@media (min-width: 485px) {
display: flex;
- flex-direction: row;
+ flex-direction: row;
}
h5 {
margin-bottom: 10px;
- }
+ }
.item {
width: 50%;
margin: 0px auto 20px;
@@ -131,7 +131,7 @@
.latest-transactions {
width: 100%;
text-align: left;
- table-layout:fixed;
+ table-layout:fixed;
tr, td, th {
border: 0px;
}
@@ -220,6 +220,11 @@
.mempool-graph {
height: 250px;
}
+.loadingGraphs{
+ height: 250px;
+ display: grid;
+ place-items: center;
+}
.inc-tx-progress-bar {
max-width: 250px;
@@ -247,7 +252,7 @@
color: #ffffff66;
font-size: 12px;
}
- .item {
+ .item {
padding: 0 5px;
width: 100%;
&:nth-child(1) {
@@ -276,25 +281,25 @@
justify-content: space-between;
@media (min-width: 376px) {
flex-direction: row;
- }
+ }
.item {
max-width: 150px;
margin: 0;
width: -webkit-fill-available;
@media (min-width: 376px) {
margin: 0 auto 0px;
- }
+ }
&:first-child{
display: none;
@media (min-width: 485px) {
display: block;
- }
+ }
@media (min-width: 768px) {
display: none;
- }
+ }
@media (min-width: 992px) {
display: block;
- }
+ }
}
&:last-child {
margin-bottom: 0;
@@ -355,4 +360,4 @@
.previous-retarget-sign {
margin-right: -2px;
font-size: 10px;
-}
\ No newline at end of file
+}
diff --git a/frontend/src/app/dashboard/dashboard.component.ts b/frontend/src/app/dashboard/dashboard.component.ts
index 38a7eaa85..7911fc204 100644
--- a/frontend/src/app/dashboard/dashboard.component.ts
+++ b/frontend/src/app/dashboard/dashboard.component.ts
@@ -6,11 +6,11 @@ import { OptimizedMempoolStats } from '../interfaces/node-api.interface';
import { MempoolInfo, TransactionStripped } from '../interfaces/websocket.interface';
import { ApiService } from '../services/api.service';
import { StateService } from '../services/state.service';
-import * as Chartist from '@mempool/chartist';
import { formatDate } from '@angular/common';
import { WebsocketService } from '../services/websocket.service';
import { SeoService } from '../services/seo.service';
import { StorageService } from '../services/storage.service';
+import { EChartsOption } from 'echarts';
interface MempoolBlocksData {
blocks: number;
@@ -34,7 +34,7 @@ interface MempoolInfoData {
memPoolInfo: MempoolInfo;
vBytesPerSecond: number;
progressWidth: string;
- progressClass: string;
+ progressColor: string;
}
interface MempoolStatsData {
@@ -74,15 +74,15 @@ export class DashboardComponent implements OnInit {
) { }
ngOnInit(): void {
-
this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$;
this.seoService.resetTitle();
this.websocketService.want(['blocks', 'stats', 'mempool-blocks', 'live-2h-chart']);
this.network$ = merge(of(''), this.stateService.networkChanged$);
this.collapseLevel = this.storageService.getValue('dashboard-collapsed') || 'one';
- this.mempoolLoadingStatus$ = this.stateService.loadingIndicators$.pipe(
- map((indicators) => indicators.mempool !== undefined ? indicators.mempool : 100)
- );
+ this.mempoolLoadingStatus$ = this.stateService.loadingIndicators$
+ .pipe(
+ map((indicators) => indicators.mempool !== undefined ? indicators.mempool : 100)
+ );
this.mempoolInfoData$ = combineLatest([
this.stateService.mempoolInfo$,
@@ -92,11 +92,21 @@ export class DashboardComponent implements OnInit {
map(([mempoolInfo, vbytesPerSecond]) => {
const percent = Math.round((Math.min(vbytesPerSecond, this.vBytesPerSecondLimit) / this.vBytesPerSecondLimit) * 100);
- let progressClass = 'bg-danger';
- if (percent <= 75) {
- progressClass = 'bg-success';
- } else if (percent <= 99) {
- progressClass = 'bg-warning';
+ let progressColor = '#7CB342';
+ if (vbytesPerSecond > 1667) {
+ progressColor = '#FDD835';
+ }
+ 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);
@@ -111,7 +121,7 @@ export class DashboardComponent implements OnInit {
memPoolInfo: mempoolInfo,
vBytesPerSecond: vbytesPerSecond,
progressWidth: percent + '%',
- progressClass: progressClass,
+ progressColor: progressColor,
mempoolSizeProgress: mempoolSizeProgress,
};
})
@@ -164,7 +174,7 @@ export class DashboardComponent implements OnInit {
}
let colorPreviousAdjustments = '#dc3545';
- if (previousRetarget){
+ if (previousRetarget) {
if (previousRetarget >= 0) {
colorPreviousAdjustments = '#3bcc49';
}
@@ -191,7 +201,6 @@ export class DashboardComponent implements OnInit {
})
);
-
this.mempoolBlocksData$ = this.stateService.mempoolBlocks$
.pipe(
map((mempoolBlocks) => {
@@ -226,50 +235,32 @@ export class DashboardComponent implements OnInit {
}, []),
);
- this.mempoolStats$ = this.stateService.connectionState$.pipe(
- filter((state) => state === 2),
- switchMap(() => this.apiService.list2HStatistics$()),
- switchMap((mempoolStats) => {
- return merge(
- this.stateService.live2Chart$
- .pipe(
- scan((acc, stats) => {
- acc.unshift(stats);
- acc = acc.slice(0, 120);
- return acc;
- }, mempoolStats)
- ),
- of(mempoolStats)
- );
- }),
- map((mempoolStats) => {
- return {
- mempool: mempoolStats,
- weightPerSecond: this.handleNewMempoolData(mempoolStats.concat([])),
- };
- }),
- 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
- }),
- ]
- };
+ this.mempoolStats$ = this.stateService.connectionState$
+ .pipe(
+ filter((state) => state === 2),
+ switchMap(() => this.apiService.list2HStatistics$()),
+ switchMap((mempoolStats) => {
+ return merge(
+ this.stateService.live2Chart$
+ .pipe(
+ scan((acc, stats) => {
+ acc.unshift(stats);
+ acc = acc.slice(0, 120);
+ return acc;
+ }, mempoolStats)
+ ),
+ of(mempoolStats)
+ );
+ }),
+ map((mempoolStats) => {
+ const data = this.handleNewMempoolData(mempoolStats.concat([]));
+ return {
+ mempool: mempoolStats,
+ weightPerSecond: this.handleNewMempoolData(mempoolStats.concat([])),
+ };
+ }),
+ share(),
+ );
}
handleNewMempoolData(mempoolStats: OptimizedMempoolStats[]) {
diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts
index 2a4bce0e8..d73c12d4e 100644
--- a/frontend/src/app/interfaces/node-api.interface.ts
+++ b/frontend/src/app/interfaces/node-api.interface.ts
@@ -6,7 +6,7 @@ export interface OptimizedMempoolStats {
vbytes_per_second: number;
total_fee: number;
mempool_byte_weight: number;
- vsizes: number[] | string[];
+ vsizes: number[];
}
interface Ancestor {
diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss
index 5b6f6b9d4..4722feabb 100644
--- a/frontend/src/styles.scss
+++ b/frontend/src/styles.scss
@@ -235,7 +235,7 @@ body {
color: #dc3545;
}
-.yellow-color {
+.yellow-color {
color: #ffd800;
}
@@ -255,168 +255,304 @@ html:lang(ru) .card-title {
font-size: 0.9rem;
}
-/* Chartist */
-$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,
-);
+/* MEMPOOL CHARTS - start */
-@import "../node_modules/@mempool/chartist/dist/scss/chartist.scss";
-
-.ct-bar-label {
- font-size: 20px;
- font-weight: bold;
- fill: #fff;
+.mempool-wrapper-tooltip-chart {
+ height: 250px;
}
-.ct-target-line {
- stroke: #f5f5f5;
- stroke-width: 3px;
- stroke-dasharray: 7px;
+.echarts {
+ height: 100%;
+ min-height: 180px;
}
-.ct-area {
- stroke: none;
- fill-opacity: 0.9;
-}
-
-.ct-label {
- fill: rgba(255, 255, 255, 0.4);
- color: rgba(255, 255, 255, 0.4);
-}
-
-.ct-point-label {
- fill: rgba(255, 255, 255, 1);
- color: rgba(255, 255, 255, 1);
- font-size: 14px;
-}
-
-.ct-grid {
- stroke: rgba(255, 255, 255, 0.2);
-}
-
-/* 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;
- }
-
- li:before {
- width: 12px;
- height: 12px;
- 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);
+.tx-wrapper-tooltip-chart,
+.fees-wrapper-tooltip-chart {
+ background: rgba(#11131f, 0.95);
+ border-radius: 4px;
+ box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
+ color: #b1b1b1;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ padding: 10px 15px;
+ text-align: left;
+ width: 200px;
+ thead {
+ th {
+ font-size: 9px;
+ color: #b1b1b1;
+ text-align: right;
+ &:first-child {
+ text-align: left;
+ left: -1px;
+ position: relative;
}
+ &:nth-child(4){
+ display: none;
+ }
+ }
+ }
+ .title {
+ font-size: 12px;
+ font-weight: 700;
+ margin-bottom: 2px;
+ color: #fff;
+ .total-value {
+ float: right;
+ }
+ }
+ .active {
+ color: yellow !important;
+ .value,
+ .total-partial {
+ color: yellow !important;
+ .symbol {
+ color: yellow !important;
+ }
+ }
+ }
+ .item {
+ line-height: 0.8;
+ .indicator-container {
+ .indicator {
+ display: inline-block;
+ margin-right: 5px;
+ border-radius: 2px;
+ margin-top: 5px;
+ width: 9px;
+ height: 9px;
+ }
+ }
+ .value {
+ text-align: right;
+ .symbol {
+ color: #7e7e7e;
+ font-size: 9px !important;
+ }
+ }
+ .symbol {
+ font-size: 9px;
+ }
+ .total-partial {
+ font-size: 10px;
+ width: 58px;
+ text-align: right;
+ }
+ .total-percentage-bar {
+ padding-left: 8px;
+ }
+ .total-progress-percentage {
+ width: 45px;
+ height: 5px;
+ text-align: right;
+ display: none;
+ }
+ .total-progress-sum {
+ width: 58px;
+ text-align: right;
+ }
+ }
+ .total-label {
+ width: 100%;
+ text-align: left;
+ color: #fff;
+ margin-top: 5px;
+ font-size: 14px;
+ span {
+ float: right;
+ }
+ .symbol {
+ margin-left: 3px;
+ font-size: 9px;
+ position: relative;
+ top: 2px;
+ }
+ }
+ thead {
+ th {
+ font-size: 9px;
+ color: #b1b1b1;
+ text-align: right;
+ &:first-child {
+ text-align: left;
+ left: -1px;
+ position: relative;
+ }
+ &:nth-child(4){
+ display: none;
+ }
+ &:nth-child(5){
+ display: none;
+ }
+ }
+ }
+ .total-percentage-bar {
+ margin: auto;
+ width: 35px;
+ position: relative;
+ span {
+ display: block;
+ background: #282d47;
+ height: 5px;
+ border-radius: 2px;
+ }
+ }
+ .total-parcial-active {
+ text-align: right;
+ margin: 5px auto 5px;
+ padding-left: 0px;
+ span {
+ font-size: 10px;
+ }
+ .symbol {
+ font-size: 9px;
+ }
+ .total-percentage-bar {
+ width: 100%;
+ span {
+ transition: 1000 all ease-in-out;
+ }
+ }
+ .progress-percentage {
+ float: left;
+ }
+ }
+}
+
+.tx-wrapper-tooltip-chart {
+ width: 115px;
+ .item {
+ display: flex;
+ }
+ .value {
+ margin-top: 5px;
+ }
+ .indicator-container {
+ border-radius: 2px;
+ }
+}
+
+.fee-distribution-chart {
+ height: 250px;
+}
+
+.fees-wrapper-tooltip-chart {
+ .item {
+ font-size: 9px;
+ line-height: 0.8;
+ margin: 0px;
+ }
+ .indicator {
+ margin-right: 5px !important;
+ border-radius: 1px !important;
+ margin-top: 0px !important;
+ }
+}
+
+.fees-wrapper-tooltip-chart-advanced,
+.tx-wrapper-tooltip-chart-advanced {
+ background: rgba(#1d1f31, 0.98);
+ width: 250px;
+
+ thead {
+ th {
+ &:nth-child(4){
+ display: table-cell;
+ }
+ &:nth-child(5){
+ display: table-cell;
+ }
+ }
+ }
+ .title {
+ font-size: 15px;
+ margin-bottom: 5px;
+ }
+ .item {
+ line-height: 1;
+ .value {
+ width: 60px;
+ .symbol {
+ font-size: 10px !important;
+ }
+ }
+ .total-partial {
+ font-size: 10px;
+ width: 58px;
+ text-align: right;
+ }
+ .total-progress-percentage {
+ width: 65px;
+ height: 4px;
+ padding: 0px 5px;
+ display: table-cell !important;
+ border-radius: 4px;
+ }
+ .total-progress-sum {
+ width: 65px;
+ height: 4px;
+ padding: 0px 5px;
+ border-radius: 4px;
+ }
+ .total-progress-percentage-bar,
+ .total-progress-sum-bar {
+ width: 35px;
+ height: 4px;
+ div {
+ width: 100%;
+ border-radius: 4px;
+ display: block;
+ background: #29324c94;
+ }
+ span {
+ height: 4px;
+ border-radius: 4px;
+ display: block;
+ }
+ }
+ }
+ .total-label {
+ margin-top: 5px;
+ font-size: 14px;
+ span {
+ float: right;
+ }
+ }
+ .total-parcial-active {
+ text-align: right;
+ margin: 5px auto 5px;
+ span {
+ font-size: 10px;
+ }
+ .total-percentage-bar {
+ width: 100%;
+ left: 0;
+ span {
+ transition: 1000 all ease-in-out;
+ }
+ }
+ }
+}
+
+.tx-wrapper-tooltip-chart-advanced {
+ width: 115px;
+ .indicator-container {
+ .indicator {
+ margin-right: 5px;
+ border-radius: 0px;
+ margin-top: 5px;
+ width: 9px;
+ height: 9px;
+ }
}
}
-.chartist-tooltip {
- position: absolute;
- display: inline-block;
- opacity: 0;
- min-width: 5em;
- padding: .5em;
- background: #F4C63D;
- color: #453D3F;
- font-family: Oxygen,Helvetica,Arial,sans-serif;
- 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; }
+/* MEMPOOL CHARTS - end */
-.ct-area, .ct-line {
- pointer-events: none; }
-
-.ct-bar {
- stroke-width: 1px;
+.grow {
+ flex-grow: 1;
}
hr {
@@ -639,7 +775,7 @@ th {
.card {
background-color: transparent;
padding: 0;
-
+
button {
text-align: left;
display: block;
@@ -653,17 +789,17 @@ th {
box-shadow: none;
}
}
-
+
.card-header {
padding: 0;
}
-
+
.collapsed{
background-color: #2d3348;
color: #1bd8f4;
}
}
-
+
.subtitle {
font-weight: bold;
margin-bottom: 3px;
@@ -675,7 +811,6 @@ th {
.pagination-container {
-
display: inline-block;
width: 100%;
justify-content: space-between;
@@ -698,4 +833,4 @@ th {
.tooltip.show {
width: 220px;
}
-}
\ No newline at end of file
+}