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 @@
-
+
@@ -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..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 `
+ ${title} + + + + + + + + + + + ${itemFormatted.reverse().join('')} + +
RangeSizeSum
+ + ${progressPercentageText} + +
`; + } + }, + 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
-
-
- +
+
-
+
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..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: @@ -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 +}