From c393483590266295c1029e9a4d4f422de53ec91d Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Tue, 14 Nov 2023 10:51:51 +0900 Subject: [PATCH 1/4] [graph] add toggle to show/hide outliers in transaction vBytes per second graph --- .../statistics/statistics.component.html | 20 +++++++++----- .../statistics/statistics.component.scss | 9 +++++++ .../statistics/statistics.component.ts | 27 ++++++++++--------- 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/frontend/src/app/components/statistics/statistics.component.html b/frontend/src/app/components/statistics/statistics.component.html index 02a26ed52..3f0ea61f5 100644 --- a/frontend/src/app/components/statistics/statistics.component.html +++ b/frontend/src/app/components/statistics/statistics.component.html @@ -109,12 +109,20 @@
-
- Transaction vBytes per second (vB/s) - -
+
+
+ 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 d883e48c3..3d4813fb5 100644 --- a/frontend/src/app/components/statistics/statistics.component.scss +++ b/frontend/src/app/components/statistics/statistics.component.scss @@ -222,4 +222,13 @@ border-top-right-radius: 0; } } +} + +.vbytes-title { + display: flex; + align-items: baseline; + justify-content: space-between; + @media (max-width: 767px) { + display: block; + } } \ No newline at end of file diff --git a/frontend/src/app/components/statistics/statistics.component.ts b/frontend/src/app/components/statistics/statistics.component.ts index 6bc58b6d7..e80ab83ad 100644 --- a/frontend/src/app/components/statistics/statistics.component.ts +++ b/frontend/src/app/components/statistics/statistics.component.ts @@ -35,7 +35,7 @@ export class StatisticsComponent implements OnInit { showCount = false; maxFeeIndex: number; dropDownOpen = false; - + outlierCappingEnabled = false; mempoolStats: OptimizedMempoolStats[] = []; mempoolVsizeFeesData: any; @@ -156,12 +156,14 @@ export class StatisticsComponent implements OnInit { } this.maxFeeIndex = maxTier; - this.capExtremeVbytesValues(); - this.mempoolTransactionsWeightPerSecondData = { labels: labels, series: [mempoolStats.map((stats) => [stats.added * 1000, stats.vbytes_per_second])], }; + + if (this.outlierCappingEnabled) { + this.capExtremeVbytesValues(); + } } saveGraphPreference() { @@ -211,24 +213,25 @@ export class StatisticsComponent implements OnInit { } }); } - + /** * All value higher that "median * capRatio" are capped */ + onOutlierToggleChange(e) { + this.outlierCappingEnabled = e.target.checked; + this.handleNewMempoolData(this.mempoolStats); + } capExtremeVbytesValues() { if (this.stateService.network.length !== 0) { return; // Only cap on Bitcoin mainnet } - let capRatio = 10; - if (['1m', '3m', '6m', '1y', '2y', '3y', '4y'].includes(this.graphWindowPreference)) { - capRatio = 4; - } + let capRatio = 4; // Find median value const vBytes: number[] = []; - for (const stat of this.mempoolStats) { - vBytes.push(stat.vbytes_per_second); + for (const stat of this.mempoolTransactionsWeightPerSecondData.series[0]) { + vBytes.push(stat[1]); } const sorted = vBytes.slice().sort((a, b) => a - b); const middle = Math.floor(sorted.length / 2); @@ -238,8 +241,8 @@ export class StatisticsComponent implements OnInit { } // Cap - for (const stat of this.mempoolStats) { - stat.vbytes_per_second = Math.min(median * capRatio, stat.vbytes_per_second); + for (const stat of this.mempoolTransactionsWeightPerSecondData.series[0]) { + stat[1] = Math.min(median * capRatio, stat[1]); } } From 2d30c0b588038caec287cac5faa96e8122f81e67 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Wed, 15 Nov 2023 14:08:44 +0900 Subject: [PATCH 2/4] [graph] use echart echart yaxis `max` property instead of modifying the data itself --- .../incoming-transactions-graph.component.ts | 26 +++++++++++++- .../statistics/statistics.component.html | 2 +- .../statistics/statistics.component.ts | 34 +------------------ 3 files changed, 27 insertions(+), 35 deletions(-) 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 index 667ad1e28..82e3e77e7 100644 --- 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 @@ -7,6 +7,8 @@ import { formatNumber } from '@angular/common'; import { StateService } from '../../services/state.service'; import { Subscription } from 'rxjs'; +const OUTLIERS_MEDIAN_MULTIPLIER = 4; + @Component({ selector: 'app-incoming-transactions-graph', templateUrl: './incoming-transactions-graph.component.html', @@ -29,6 +31,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On @Input() left: number | string = '0'; @Input() template: ('widget' | 'advanced') = 'widget'; @Input() windowPreferenceOverride: string; + @Input() outlierCappingEnabled: boolean = false; isLoading = true; mempoolStatsChartOption: EChartsOption = {}; @@ -40,6 +43,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On MA: number[][] = []; weightMode: boolean = false; rateUnitSub: Subscription; + medianVbytesPerSecond: number | undefined; constructor( @Inject(LOCALE_ID) private locale: string, @@ -65,16 +69,35 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On this.windowPreference = this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference'); const windowSize = Math.max(10, Math.floor(this.data.series[0].length / 8)); this.MA = this.calculateMA(this.data.series[0], windowSize); + if (this.outlierCappingEnabled === true) { + this.computeMedianVbytesPerSecond(this.data.series[0]); + } this.mountChart(); } rendered() { if (!this.data) { - return; + return; } this.isLoading = false; } + /** + * Calculate the median value of the vbytes per second chart to hide outliers + */ + computeMedianVbytesPerSecond(data: number[][]): void { + const vBytes: number[] = []; + for (const value of data) { + vBytes.push(value[1]); + } + const sorted = vBytes.slice().sort((a, b) => a - b); + const middle = Math.floor(sorted.length / 2); + this.medianVbytesPerSecond = sorted[middle]; + if (sorted.length % 2 === 0) { + this.medianVbytesPerSecond = (sorted[middle - 1] + sorted[middle]) / 2; + } + } + /// calculate the moving average of the provided data based on windowSize calculateMA(data: number[][], windowSize: number = 100): number[][] { //update const variables that are not changed @@ -232,6 +255,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On } ], yAxis: { + max: this.outlierCappingEnabled ? Math.round(this.medianVbytesPerSecond * OUTLIERS_MEDIAN_MULTIPLIER) : undefined, type: 'value', axisLabel: { fontSize: 11, diff --git a/frontend/src/app/components/statistics/statistics.component.html b/frontend/src/app/components/statistics/statistics.component.html index 3f0ea61f5..c726e354e 100644 --- a/frontend/src/app/components/statistics/statistics.component.html +++ b/frontend/src/app/components/statistics/statistics.component.html @@ -128,7 +128,7 @@
+ [data]="mempoolTransactionsWeightPerSecondData" [outlierCappingEnabled]="outlierCappingEnabled">
diff --git a/frontend/src/app/components/statistics/statistics.component.ts b/frontend/src/app/components/statistics/statistics.component.ts index e80ab83ad..8e01e068b 100644 --- a/frontend/src/app/components/statistics/statistics.component.ts +++ b/frontend/src/app/components/statistics/statistics.component.ts @@ -160,10 +160,6 @@ export class StatisticsComponent implements OnInit { labels: labels, series: [mempoolStats.map((stats) => [stats.added * 1000, stats.vbytes_per_second])], }; - - if (this.outlierCappingEnabled) { - this.capExtremeVbytesValues(); - } } saveGraphPreference() { @@ -214,36 +210,8 @@ export class StatisticsComponent implements OnInit { }); } - /** - * All value higher that "median * capRatio" are capped - */ - onOutlierToggleChange(e) { + onOutlierToggleChange(e): void { this.outlierCappingEnabled = e.target.checked; - this.handleNewMempoolData(this.mempoolStats); - } - capExtremeVbytesValues() { - if (this.stateService.network.length !== 0) { - return; // Only cap on Bitcoin mainnet - } - - let capRatio = 4; - - // Find median value - const vBytes: number[] = []; - for (const stat of this.mempoolTransactionsWeightPerSecondData.series[0]) { - vBytes.push(stat[1]); - } - const sorted = vBytes.slice().sort((a, b) => a - b); - const middle = Math.floor(sorted.length / 2); - let median = sorted[middle]; - if (sorted.length % 2 === 0) { - median = (sorted[middle - 1] + sorted[middle]) / 2; - } - - // Cap - for (const stat of this.mempoolTransactionsWeightPerSecondData.series[0]) { - stat[1] = Math.min(median * capRatio, stat[1]); - } } onSaveChart(name) { From dc26c6f105c666cbce9aa1248d95ce850f3558fb Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Wed, 15 Nov 2023 18:46:33 +0900 Subject: [PATCH 3/4] [graph] don't change yaxis scale if no outliers - save state in localstorage --- .../incoming-transactions-graph.component.ts | 10 +++++++++- .../components/statistics/statistics.component.html | 2 +- .../app/components/statistics/statistics.component.ts | 2 ++ 3 files changed, 12 insertions(+), 2 deletions(-) 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 index 82e3e77e7..f6d2de5df 100644 --- 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 @@ -255,7 +255,15 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On } ], yAxis: { - max: this.outlierCappingEnabled ? Math.round(this.medianVbytesPerSecond * OUTLIERS_MEDIAN_MULTIPLIER) : undefined, + max: (value) => { + if (!this.outlierCappingEnabled) { + return undefined; + } + if (value.max < this.medianVbytesPerSecond * OUTLIERS_MEDIAN_MULTIPLIER) { + return undefined; + } + return Math.round(this.medianVbytesPerSecond * OUTLIERS_MEDIAN_MULTIPLIER); + }, type: 'value', axisLabel: { fontSize: 11, diff --git a/frontend/src/app/components/statistics/statistics.component.html b/frontend/src/app/components/statistics/statistics.component.html index c726e354e..0bb10a1c3 100644 --- a/frontend/src/app/components/statistics/statistics.component.html +++ b/frontend/src/app/components/statistics/statistics.component.html @@ -117,7 +117,7 @@
- + diff --git a/frontend/src/app/components/statistics/statistics.component.ts b/frontend/src/app/components/statistics/statistics.component.ts index 8e01e068b..fdd5a018e 100644 --- a/frontend/src/app/components/statistics/statistics.component.ts +++ b/frontend/src/app/components/statistics/statistics.component.ts @@ -67,6 +67,7 @@ export class StatisticsComponent implements OnInit { this.seoService.setDescription($localize`:@@meta.description.bitcoin.graphs.mempool:See mempool size (in MvB) and transactions per second (in vB/s) visualized over time.`); this.stateService.networkChanged$.subscribe((network) => this.network = network); this.graphWindowPreference = this.storageService.getValue('graphWindowPreference') ? this.storageService.getValue('graphWindowPreference').trim() : '2h'; + this.outlierCappingEnabled = this.storageService.getValue('cap-outliers') === 'true'; this.radioGroupForm = this.formBuilder.group({ dateSpan: this.graphWindowPreference @@ -212,6 +213,7 @@ export class StatisticsComponent implements OnInit { onOutlierToggleChange(e): void { this.outlierCappingEnabled = e.target.checked; + this.storageService.setValue('cap-outliers', e.target.checked); } onSaveChart(name) { From 4cd8d70de56d511c04aba8f5ea27e6cd0bfba28e Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Wed, 15 Nov 2023 18:47:56 +0900 Subject: [PATCH 4/4] cleanup if/else --- .../incoming-transactions-graph.component.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) 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 index f6d2de5df..10fe22067 100644 --- 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 @@ -256,13 +256,11 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On ], yAxis: { max: (value) => { - if (!this.outlierCappingEnabled) { + if (!this.outlierCappingEnabled || value.max < this.medianVbytesPerSecond * OUTLIERS_MEDIAN_MULTIPLIER) { return undefined; + } else { + return Math.round(this.medianVbytesPerSecond * OUTLIERS_MEDIAN_MULTIPLIER); } - if (value.max < this.medianVbytesPerSecond * OUTLIERS_MEDIAN_MULTIPLIER) { - return undefined; - } - return Math.round(this.medianVbytesPerSecond * OUTLIERS_MEDIAN_MULTIPLIER); }, type: 'value', axisLabel: {