diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6e6c130f4..e27b73b20 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,7 +24,6 @@ "@fortawesome/fontawesome-svg-core": "^1.2.35", "@fortawesome/free-solid-svg-icons": "^5.15.3", "@juggle/resize-observer": "^3.3.1", - "@mempool/chartist": "^0.11.4", "@mempool/mempool.js": "^2.2.4", "@ng-bootstrap/ng-bootstrap": "^7.0.0", "@nguniversal/express-engine": "11.2.1", @@ -2260,14 +2259,6 @@ "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.3.1.tgz", "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": { "version": "2.2.4", "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", "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": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/@mempool/mempool.js/-/mempool.js-2.2.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index 8d6edfb3d..5cf7e0c95 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -68,7 +68,6 @@ "@fortawesome/fontawesome-svg-core": "^1.2.35", "@fortawesome/free-solid-svg-icons": "^5.15.3", "@juggle/resize-observer": "^3.3.1", - "@mempool/chartist": "^0.11.4", "@mempool/mempool.js": "^2.2.4", "@ng-bootstrap/ng-bootstrap": "^7.0.0", "@nguniversal/express-engine": "11.2.1", 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 d8b144847..d263db3ad 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -27,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'; @@ -79,10 +79,10 @@ import { SponsorComponent } from './components/sponsor/sponsor.component'; TimeSpanComponent, AddressLabelsComponent, MempoolBlocksComponent, - ChartistComponent, FooterComponent, MempoolBlockComponent, FeeDistributionGraphComponent, + IncomingTransactionsGraphComponent, MempoolGraphComponent, AssetComponent, AssetsComponent, 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..84cf34cfe 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..873d42fb8 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,80 @@ -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; - 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..7c6de093e --- /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..dd4001821 --- /dev/null +++ b/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts @@ -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) => `
`; + let itemFormatted = '
' + params[0].axisValue + '
'; + params.map((item: any, index: number) => { + if (index < 26) { + itemFormatted += `
+ ${colorSpan(item.color)} +
+
${item.value}
+
`; + } + }); + if (this.theme !== '') { + return `
${itemFormatted}
`; + } + return `
${itemFormatted}
`; + } + }, + 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' + } + }, + }; + } +} 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 @@
-
+
@@ -40,8 +40,8 @@
-
- +
+
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..0ea3867a8 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..2b384d7e8 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,18 @@ 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'; + +interface AxisObject { + axisDimension: string; + axisIndex: number; + seriesData: any; + value: string; +} @Component({ selector: 'app-mempool-graph', @@ -12,111 +20,50 @@ import { StorageService } from 'src/app/services/storage.service'; changeDetection: ChangeDetectionStrategy.OnPush, }) 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() showLegend = true; - @Input() offsetX = 40; @Input() small = false; - mempoolVsizeFeesOptions: any; mempoolVsizeFeesData: any; + mempoolVsizeFeesOptions: EChartsOption; - isMobile = window.innerWidth <= 767.98; inverted: boolean; constructor( private vbytesPipe: VbytesPipe, private stateService: StateService, @Inject(LOCALE_ID) private locale: string, - private storageService: StorageService, ) { } 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); - } - 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.inverted = this.storageService.getValue('inverted-graph') === 'true'; this.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([])); + this.mountFeeChart(); } 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 }; } @@ -134,12 +81,128 @@ export class MempoolGraphComponent implements OnInit, OnChanges { feesArray.push(0); } }); - if (this.inverted && finalArray.length) { - feesArray = feesArray.map((value, i) => value + finalArray[finalArray.length - 1][i]); - } + // 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 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, 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) => `
`; + const legendName = (index: number) => legendNames[index]; + let itemFormatted = '
' + params[0].axisValue + '
'; + params.map((item: any, index: number) => { + if (feeLevels[index - 1] < this.limitFee) { + itemFormatted += `
+ ${colorSpan(index - 1)} ${legendName(index)} +
+
${this.vbytesPipe.transform(item.value, 2, 'vB', 'MvB', true)}
+
`; + } + }); + return `
${itemFormatted}
`; + } + }, + 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 + }; + } } 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..b932c746c 100644 --- a/frontend/src/app/components/statistics/statistics.component.html +++ b/frontend/src/app/components/statistics/statistics.component.html @@ -36,12 +36,12 @@ 1Y
- +
-
- +
+
@@ -53,12 +53,8 @@ Transaction vBytes per second (vB/s)
-
- - +
+
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..733762764 100644 --- a/frontend/src/app/components/statistics/statistics.component.ts +++ b/frontend/src/app/components/statistics/statistics.component.ts @@ -9,7 +9,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,8 +30,6 @@ export class StatisticsComponent implements OnInit { mempoolUnconfirmedTransactionsData: any; mempoolTransactionsWeightPerSecondData: any; - transactionsWeightPerSecondOptions: any; - radioGroupForm: FormGroup; inverted: boolean; graphWindowPreference: String; @@ -64,43 +61,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) => { diff --git a/frontend/src/app/components/television/television.component.html b/frontend/src/app/components/television/television.component.html index e8a423b0b..d12564c30 100644 --- a/frontend/src/app/components/television/television.component.html +++ b/frontend/src/app/components/television/television.component.html @@ -4,12 +4,10 @@
-
- -
- +
+
+
-
diff --git a/frontend/src/app/components/television/television.component.scss b/frontend/src/app/components/television/television.component.scss index f45908655..8629fbcb7 100644 --- a/frontend/src/app/components/television/television.component.scss +++ b/frontend/src/app/components/television/television.component.scss @@ -16,30 +16,21 @@ } .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; } .blockchain-wrapper { - display: flex; height: 100%; min-height: 240px; position: relative; top: -20px; @media(min-height: 800px) { - top: 10px; + top: 30px; } - + .position-container { position: absolute; left: 50%; @@ -89,4 +80,4 @@ 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..5eab01618 100644 --- a/frontend/src/app/dashboard/dashboard.component.html +++ b/frontend/src/app/dashboard/dashboard.component.html @@ -47,8 +47,8 @@
-
- +
+
@@ -59,12 +59,8 @@

-
- - +
+
@@ -197,13 +193,14 @@
-
 
+
 
{{ mempoolInfoData.value.vBytesPerSecond | ceil | number }} vB/s
+
Difficulty Adjustment
@@ -228,11 +225,11 @@ - {{ epochData.change | absolute | number: '1.2-2' }} + {{ epochData.change | absolute | number: '1.2-2' }} %
- Previous: + Previous: @@ -257,6 +254,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/styles.scss b/frontend/src/styles.scss index 5b6f6b9d4..09255a76f 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,64 @@ 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 */ -@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; +.tx-wrapper-tooltip-chart, .fees-wrapper-tooltip-chart { + display: flex; + justify-content: space-between; + flex-direction: column; + background: rgba(#11131f, 0.85); + color: #fff; + padding: 10px 15px; + border-radius: 4px; + box-shadow: 1px 1px 10px rgba(0,0,0,0.2); + .item { + text-align: left; + display: flex; + .indicator { + display: block; + margin-right: 5px; + border-radius: 10px; + margin-top: 5px; + width: 9px; + height: 9px; } - - 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); + .value { + text-align: right; + span { + color: #212121 !important; } + } } } +.grow { + flex-grow: 1; +} -.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; } +.fees-wrapper-tooltip-chart { + .item { + font-size: 9px; + line-height: 1; + } + .indicator { + margin-right: 5px !important; + border-radius: 10px !important; + margin-top: 0px !important; + } +} -.ct-area, .ct-line { - pointer-events: none; } - -.ct-bar { - stroke-width: 1px; +.fee-distribution-chart { + height: 250px; } hr { @@ -639,7 +535,7 @@ th { .card { background-color: transparent; padding: 0; - + button { text-align: left; display: block; @@ -653,17 +549,17 @@ th { box-shadow: none; } } - + .card-header { padding: 0; } - + .collapsed{ background-color: #2d3348; color: #1bd8f4; } } - + .subtitle { font-weight: bold; margin-bottom: 3px; @@ -675,7 +571,6 @@ th { .pagination-container { - display: inline-block; width: 100%; justify-content: space-between; @@ -698,4 +593,4 @@ th { .tooltip.show { width: 220px; } -} \ No newline at end of file +}