From d517a1862b434936ab39588f609ba3164ed7186c Mon Sep 17 00:00:00 2001 From: MaysWind Date: Sat, 7 Mar 2026 22:18:33 +0800 Subject: [PATCH] add 90th percentile amount, range, interquartile range, variance, and standard deviation to the value metrics in insights explorer --- src/components/base/PieChartBase.ts | 2 +- src/components/desktop/AxisChart.vue | 2 +- src/components/desktop/RadarChart.vue | 2 +- src/core/explorer.ts | 28 +++++++++++++------ src/stores/explorer.ts | 28 +++++++++++++++++++ .../insights/tabs/ExplorerChartTab.vue | 8 +++--- 6 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/components/base/PieChartBase.ts b/src/components/base/PieChartBase.ts index 60593c4f..5f6fc66d 100644 --- a/src/components/base/PieChartBase.ts +++ b/src/components/base/PieChartBase.ts @@ -81,7 +81,7 @@ export function usePieChartBase(props: CommonPieChartProps) { accumulatedPaintPercent += finalItem.paintPercent; finalItem.displayPercent = formatPercentToLocalizedNumerals(finalItem.percent, 2, '<0.01'); - finalItem.displayValue = props.amountValue ? formatAmountToLocalizedNumeralsWithCurrency(value, props.defaultCurrency) : formatNumberToLocalizedNumerals(value); + finalItem.displayValue = props.amountValue ? formatAmountToLocalizedNumeralsWithCurrency(value, props.defaultCurrency) : formatNumberToLocalizedNumerals(value, 2); validItems.push(finalItem); } diff --git a/src/components/desktop/AxisChart.vue b/src/components/desktop/AxisChart.vue index 48805afa..f77a8686 100644 --- a/src/components/desktop/AxisChart.vue +++ b/src/components/desktop/AxisChart.vue @@ -405,7 +405,7 @@ function getDisplayValue(value: number): string { return formatAmountToLocalizedNumeralsWithCurrency(value, props.defaultCurrency); } - return formatNumberToLocalizedNumerals(value); + return formatNumberToLocalizedNumerals(value, 2); } function clickItem(e: ECElementEvent): void { diff --git a/src/components/desktop/RadarChart.vue b/src/components/desktop/RadarChart.vue index fbacde83..dc732549 100644 --- a/src/components/desktop/RadarChart.vue +++ b/src/components/desktop/RadarChart.vue @@ -84,7 +84,7 @@ const radarData = computed(() => { const finalPercent = (isNumber(percent) && percent >= 0) ? percent : (value > 0 ? value / totalValidValue * 100 : 0); const displayPercent = formatPercentToLocalizedNumerals(finalPercent, 2, '<0.01'); - const displayValue = props.amountValue ? formatAmountToLocalizedNumeralsWithCurrency(value, props.defaultCurrency) : formatNumberToLocalizedNumerals(value); + const displayValue = props.amountValue ? formatAmountToLocalizedNumeralsWithCurrency(value, props.defaultCurrency) : formatNumberToLocalizedNumerals(value, 2); indicators.push({ name: name, diff --git a/src/core/explorer.ts b/src/core/explorer.ts index ceaeaf5c..35a5decf 100644 --- a/src/core/explorer.ts +++ b/src/core/explorer.ts @@ -260,31 +260,43 @@ export enum TransactionExplorerValueMetricType { SourceAmountSum = 'sourceAmountSum', SourceAmountAverage = 'sourceAmountAverage', SourceAmountMedian = 'sourceAmountMedian', + SourceAmount90thPercentile = 'source90thPercentileAmount', SourceAmountMinimum = 'sourceAmountMinimum', - SourceAmountMaximum = 'sourceAmountMaximum' + SourceAmountMaximum = 'sourceAmountMaximum', + SourceAmountRange = 'sourceAmountRange', + SourceAmountInterquartileRange = 'sourceAmountInterquartileRange', + SourceAmountVariance = 'sourceAmountVariance', + SourceAmountStandardDeviation = 'sourceAmountStandardDeviation' } export class TransactionExplorerValueMetric implements NameValue { private static readonly allInstances: TransactionExplorerValueMetric[] = []; private static readonly allInstancesByValue: Record = {}; - public static readonly TransactionCount = new TransactionExplorerValueMetric('Transaction Count', TransactionExplorerValueMetricType.TransactionCount, false); - public static readonly SourceAmountSum = new TransactionExplorerValueMetric('Total Amount', TransactionExplorerValueMetricType.SourceAmountSum, true); - public static readonly SourceAmountAverage = new TransactionExplorerValueMetric('Average Amount', TransactionExplorerValueMetricType.SourceAmountAverage, true); - public static readonly SourceAmountMedian = new TransactionExplorerValueMetric('Median Amount', TransactionExplorerValueMetricType.SourceAmountMedian, true); - public static readonly SourceAmountMinimum = new TransactionExplorerValueMetric('Minimum Amount', TransactionExplorerValueMetricType.SourceAmountMinimum, true); - public static readonly SourceAmountMaximum = new TransactionExplorerValueMetric('Maximum Amount', TransactionExplorerValueMetricType.SourceAmountMaximum, true); + public static readonly TransactionCount = new TransactionExplorerValueMetric('Transaction Count', TransactionExplorerValueMetricType.TransactionCount, false, true); + public static readonly SourceAmountSum = new TransactionExplorerValueMetric('Total Amount', TransactionExplorerValueMetricType.SourceAmountSum, true, true); + public static readonly SourceAmountAverage = new TransactionExplorerValueMetric('Average Amount', TransactionExplorerValueMetricType.SourceAmountAverage, true, true); + public static readonly SourceAmountMedian = new TransactionExplorerValueMetric('Median Amount', TransactionExplorerValueMetricType.SourceAmountMedian, true, true); + public static readonly SourceAmount90thPercentile = new TransactionExplorerValueMetric('90th Percentile Amount', TransactionExplorerValueMetricType.SourceAmount90thPercentile, true, true); + public static readonly SourceAmountMinimum = new TransactionExplorerValueMetric('Minimum Amount', TransactionExplorerValueMetricType.SourceAmountMinimum, true, true); + public static readonly SourceAmountMaximum = new TransactionExplorerValueMetric('Maximum Amount', TransactionExplorerValueMetricType.SourceAmountMaximum, true, true); + public static readonly SourceAmountRange = new TransactionExplorerValueMetric('Range (Max - Min)', TransactionExplorerValueMetricType.SourceAmountRange, true, true); + public static readonly SourceAmountInterquartileRange = new TransactionExplorerValueMetric('Interquartile Range (Q3 - Q1)', TransactionExplorerValueMetricType.SourceAmountInterquartileRange, true, true); + public static readonly SourceAmountVariance = new TransactionExplorerValueMetric('Variance', TransactionExplorerValueMetricType.SourceAmountVariance, false, false); + public static readonly SourceAmountStandardDeviation = new TransactionExplorerValueMetric('Standard Deviation', TransactionExplorerValueMetricType.SourceAmountStandardDeviation, false, false); public static readonly Default = TransactionExplorerValueMetric.SourceAmountSum; public readonly name: string; public readonly value: TransactionExplorerValueMetricType; public readonly isAmount: boolean; + public readonly supportSum: boolean; - private constructor(name: string, value: TransactionExplorerValueMetricType, isAmount: boolean) { + private constructor(name: string, value: TransactionExplorerValueMetricType, isAmount: boolean, supportSum: boolean) { this.name = name; this.value = value; this.isAmount = isAmount; + this.supportSum = supportSum; TransactionExplorerValueMetric.allInstances.push(this); TransactionExplorerValueMetric.allInstancesByValue[value] = this; diff --git a/src/stores/explorer.ts b/src/stores/explorer.ts index a7044434..273eca1c 100644 --- a/src/stores/explorer.ts +++ b/src/stores/explorer.ts @@ -818,10 +818,38 @@ export const useExplorersStore = defineStore('explorers', () => { } else { value = 0; } + } else if (valueMetric === TransactionExplorerValueMetric.SourceAmount90thPercentile) { + if (allSourceAmountsInDefaultCurrency.length > 0) { + allSourceAmountsInDefaultCurrency.sort((a, b) => a - b); + value = allSourceAmountsInDefaultCurrency[Math.floor(allSourceAmountsInDefaultCurrency.length * 9 / 10)] as number; + } else { + value = 0; + } } else if (valueMetric === TransactionExplorerValueMetric.SourceAmountMinimum) { value = minimumSourceAmountInDefaultCurrency === Number.MAX_SAFE_INTEGER ? 0 : minimumSourceAmountInDefaultCurrency; } else if (valueMetric === TransactionExplorerValueMetric.SourceAmountMaximum) { value = maximumSourceAmountInDefaultCurrency === Number.MIN_SAFE_INTEGER ? 0 : maximumSourceAmountInDefaultCurrency; + } else if (valueMetric === TransactionExplorerValueMetric.SourceAmountRange) { + const finalMinimumSourceAmountInDefaultCurrency = minimumSourceAmountInDefaultCurrency === Number.MAX_SAFE_INTEGER ? 0 : minimumSourceAmountInDefaultCurrency; + const finalMaximumSourceAmountInDefaultCurrency = maximumSourceAmountInDefaultCurrency === Number.MIN_SAFE_INTEGER ? 0 : maximumSourceAmountInDefaultCurrency; + value = finalMaximumSourceAmountInDefaultCurrency - finalMinimumSourceAmountInDefaultCurrency; + } else if (valueMetric === TransactionExplorerValueMetric.SourceAmountInterquartileRange) { + if (allSourceAmountsInDefaultCurrency.length > 0) { + allSourceAmountsInDefaultCurrency.sort((a, b) => a - b); + const q1 = allSourceAmountsInDefaultCurrency[Math.floor(allSourceAmountsInDefaultCurrency.length / 4)] as number; + const q3 = allSourceAmountsInDefaultCurrency[Math.floor(allSourceAmountsInDefaultCurrency.length * 3 / 4)] as number; + value = q3 - q1; + } else { + value = 0; + } + } else if (valueMetric === TransactionExplorerValueMetric.SourceAmountVariance) { + const averageSourceAmountInDefaultCurrency = allSourceAmountsInDefaultCurrency.length > 0 ? totalSourceAmountSumInDefaultCurrency / allSourceAmountsInDefaultCurrency.length / 100.0 : 0; + const sumOfSquaredDifferences = allSourceAmountsInDefaultCurrency.reduce((sum, amount) => sum + Math.pow(amount / 100.0 - averageSourceAmountInDefaultCurrency, 2), 0); + value = allSourceAmountsInDefaultCurrency.length > 0 ? sumOfSquaredDifferences / allSourceAmountsInDefaultCurrency.length : 0; + } else if (valueMetric === TransactionExplorerValueMetric.SourceAmountStandardDeviation) { + const averageSourceAmountInDefaultCurrency = allSourceAmountsInDefaultCurrency.length > 0 ? totalSourceAmountSumInDefaultCurrency / allSourceAmountsInDefaultCurrency.length / 100.0 : 0; + const sumOfSquaredDifferences = allSourceAmountsInDefaultCurrency.reduce((sum, amount) => sum + Math.pow(amount / 100.0 - averageSourceAmountInDefaultCurrency, 2), 0); + value = allSourceAmountsInDefaultCurrency.length > 0 ? Math.sqrt(sumOfSquaredDifferences / allSourceAmountsInDefaultCurrency.length) : 0; } dataItems.push({ diff --git a/src/views/desktop/insights/tabs/ExplorerChartTab.vue b/src/views/desktop/insights/tabs/ExplorerChartTab.vue index 0cd10a74..2da80162 100644 --- a/src/views/desktop/insights/tabs/ExplorerChartTab.vue +++ b/src/views/desktop/insights/tabs/ExplorerChartTab.vue @@ -92,7 +92,7 @@ :show-value="true" :show-percent="true" :enable-click-item="true" - :amount-value="currentExplorer.valueMetric !== TransactionExplorerValueMetric.TransactionCount.value" + :amount-value="TransactionExplorerValueMetric.valueOf(currentExplorer.valueMetric)?.isAmount" :default-currency="defaultCurrency" id-field="id" name-field="name" @@ -120,7 +120,7 @@ :items="categoryDimensionTransactionExplorerData && categoryDimensionTransactionExplorerData.length ? categoryDimensionTransactionExplorerData : []" :show-value="true" :show-percent="true" - :amount-value="currentExplorer.valueMetric !== TransactionExplorerValueMetric.TransactionCount.value" + :amount-value="TransactionExplorerValueMetric.valueOf(currentExplorer.valueMetric)?.isAmount" :default-currency="defaultCurrency" name-field="name" value-field="totalAmount" @@ -146,12 +146,12 @@ :one-hundred-percent-stacked="axisChart100PercentStacked" :sorting-type="currentExplorer.chartSortingType" :show-value="true" - :show-total-amount-in-tooltip="true" + :show-total-amount-in-tooltip="TransactionExplorerValueMetric.valueOf(currentExplorer.valueMetric)?.supportSum" :total-name-in-tooltip="currentExplorer.valueMetric === TransactionExplorerValueMetric.TransactionCount.value ? tt('Total Transactions') : tt('Total Amount')" :category-type-name="currentTransactionExplorerCategoryDimensionName" :all-category-names="categoriedNamesSortedByDisplayOrder" :items="seriesDimensionTransactionExplorerData" - :amount-value="currentExplorer.valueMetric !== TransactionExplorerValueMetric.TransactionCount.value" + :amount-value="TransactionExplorerValueMetric.valueOf(currentExplorer.valueMetric)?.isAmount" :default-currency="defaultCurrency" :enable-click-item="true" id-field="id"