From 6a4ab4c1456d96dad74ac057f0c4253727696fed Mon Sep 17 00:00:00 2001 From: MaysWind Date: Sun, 19 Apr 2026 01:51:56 +0800 Subject: [PATCH] add amount range to axis / category / series in insights explorer --- src/core/explorer.ts | 80 ++-- src/locales/de.json | 11 + src/locales/en.json | 11 + src/locales/es.json | 11 + src/locales/fr.json | 11 + src/locales/it.json | 11 + src/locales/ja.json | 11 + src/locales/kn.json | 11 + src/locales/ko.json | 11 + src/locales/nl.json | 11 + src/locales/pt_BR.json | 11 + src/locales/ru.json | 11 + src/locales/sl.json | 11 + src/locales/ta.json | 11 + src/locales/th.json | 11 + src/locales/tr.json | 11 + src/locales/uk.json | 11 + src/locales/vi.json | 11 + src/locales/zh_Hans.json | 11 + src/locales/zh_Hant.json | 11 + src/models/explorer.ts | 13 +- src/stores/explorer.ts | 366 +++++++++++++++++- .../insights/tabs/ExplorerChartTab.vue | 72 +++- 23 files changed, 686 insertions(+), 54 deletions(-) diff --git a/src/core/explorer.ts b/src/core/explorer.ts index 905a5761..0feefff5 100644 --- a/src/core/explorer.ts +++ b/src/core/explorer.ts @@ -240,51 +240,75 @@ export enum TransactionExplorerDataDimensionType { DestinationAccount = 'destinationAccount', DestinationAccountCategory = 'destinationAccountCategory', DestinationAccountCurrency = 'destinationAccountCurrency', + PrimaryCategory = 'primaryCategory', + SecondaryCategory = 'secondaryCategory', SourceAmount = 'sourceAmount', DestinationAmount = 'destinationAmount', - PrimaryCategory = 'primaryCategory', - SecondaryCategory = 'secondaryCategory' + SourceAmountRangeEqualFrequency = 'sourceAmountRangeEqualFrequency', + SourceAmountRangeEqualWidth = 'sourceAmountRangeEqualWidth', + SourceAmountRangeLogScale = 'sourceAmountRangeLogScale', + SourceAmountRangeStandardDeviation = 'sourceAmountRangeStandardDeviation', + SourceAmountRangeNaturalBreaks = 'sourceAmountRangeNaturalBreaks', + DestinationAmountRangeEqualFrequency = 'destinationAmountRangeEqualFrequency', + DestinationAmountRangeEqualWidth = 'destinationAmountRangeEqualWidth', + DestinationAmountRangeLogScale = 'destinationAmountRangeLogScale', + DestinationAmountRangeStandardDeviation = 'destinationAmountRangeStandardDeviation', + DestinationAmountRangeNaturalBreaks = 'destinationAmountRangeNaturalBreaks' } export class TransactionExplorerDataDimension implements NameValue { private static readonly allInstances: TransactionExplorerDataDimension[] = []; private static readonly allInstancesByValue: Record = {}; - public static readonly None = new TransactionExplorerDataDimension('None', TransactionExplorerDataDimensionType.None); - public static readonly Query = new TransactionExplorerDataDimension('Query', TransactionExplorerDataDimensionType.Query); - public static readonly DateTime = new TransactionExplorerDataDimension('Transaction Time', TransactionExplorerDataDimensionType.DateTime); - public static readonly DateTimeByYearMonthDay = new TransactionExplorerDataDimension('Transaction Date', TransactionExplorerDataDimensionType.DateTimeByYearMonthDay); - public static readonly DateTimeByYearMonth = new TransactionExplorerDataDimension('Transaction Year-Month', TransactionExplorerDataDimensionType.DateTimeByYearMonth); - public static readonly DateTimeByYearQuarter = new TransactionExplorerDataDimension('Transaction Year-Quarter', TransactionExplorerDataDimensionType.DateTimeByYearQuarter); - public static readonly DateTimeByYear = new TransactionExplorerDataDimension('Transaction Year', TransactionExplorerDataDimensionType.DateTimeByYear); - public static readonly DateTimeByFiscalYear = new TransactionExplorerDataDimension('Transaction Fiscal Year', TransactionExplorerDataDimensionType.DateTimeByFiscalYear); - public static readonly DateTimeByDayOfWeek = new TransactionExplorerDataDimension('Transaction Day of Week', TransactionExplorerDataDimensionType.DateTimeByDayOfWeek); - public static readonly DateTimeByDayOfMonth = new TransactionExplorerDataDimension('Transaction Day of Month', TransactionExplorerDataDimensionType.DateTimeByDayOfMonth); - public static readonly DateTimeByMonthOfYear = new TransactionExplorerDataDimension('Transaction Month of Year', TransactionExplorerDataDimensionType.DateTimeByMonthOfYear); - public static readonly DateTimeByQuarterOfYear = new TransactionExplorerDataDimension('Transaction Quarter of Year', TransactionExplorerDataDimensionType.DateTimeByQuarterOfYear); - public static readonly DateTimeByHourOfDay = new TransactionExplorerDataDimension('Transaction Hour of Day', TransactionExplorerDataDimensionType.DateTimeByHourOfDay); - public static readonly TimezoneOffset = new TransactionExplorerDataDimension('Transaction Timezone', TransactionExplorerDataDimensionType.TimezoneOffset); - public static readonly TransactionType = new TransactionExplorerDataDimension('Transaction Type', TransactionExplorerDataDimensionType.TransactionType); - public static readonly SourceAccount = new TransactionExplorerDataDimension('Source Account', TransactionExplorerDataDimensionType.SourceAccount); - public static readonly SourceAccountCategory = new TransactionExplorerDataDimension('Source Account Category', TransactionExplorerDataDimensionType.SourceAccountCategory); - public static readonly SourceAccountCurrency = new TransactionExplorerDataDimension('Source Account Currency', TransactionExplorerDataDimensionType.SourceAccountCurrency); - public static readonly DestinationAccount = new TransactionExplorerDataDimension('Destination Account', TransactionExplorerDataDimensionType.DestinationAccount); - public static readonly DestinationAccountCategory = new TransactionExplorerDataDimension('Destination Account Category', TransactionExplorerDataDimensionType.DestinationAccountCategory); - public static readonly DestinationAccountCurrency = new TransactionExplorerDataDimension('Destination Account Currency', TransactionExplorerDataDimensionType.DestinationAccountCurrency); - public static readonly PrimaryCategory = new TransactionExplorerDataDimension('Primary Category', TransactionExplorerDataDimensionType.PrimaryCategory); - public static readonly SecondaryCategory = new TransactionExplorerDataDimension('Secondary Category', TransactionExplorerDataDimensionType.SecondaryCategory); - public static readonly SourceAmount = new TransactionExplorerDataDimension('Amount', TransactionExplorerDataDimensionType.SourceAmount); - public static readonly DestinationAmount = new TransactionExplorerDataDimension('Transfer In Amount', TransactionExplorerDataDimensionType.DestinationAmount); + public static readonly None = new TransactionExplorerDataDimension('None', TransactionExplorerDataDimensionType.None, false, false); + public static readonly Query = new TransactionExplorerDataDimension('Query', TransactionExplorerDataDimensionType.Query, false, false); + public static readonly DateTime = new TransactionExplorerDataDimension('Transaction Time', TransactionExplorerDataDimensionType.DateTime, false, false); + public static readonly DateTimeByYearMonthDay = new TransactionExplorerDataDimension('Transaction Date', TransactionExplorerDataDimensionType.DateTimeByYearMonthDay, false, false); + public static readonly DateTimeByYearMonth = new TransactionExplorerDataDimension('Transaction Year-Month', TransactionExplorerDataDimensionType.DateTimeByYearMonth, false, false); + public static readonly DateTimeByYearQuarter = new TransactionExplorerDataDimension('Transaction Year-Quarter', TransactionExplorerDataDimensionType.DateTimeByYearQuarter, false, false); + public static readonly DateTimeByYear = new TransactionExplorerDataDimension('Transaction Year', TransactionExplorerDataDimensionType.DateTimeByYear, false, false); + public static readonly DateTimeByFiscalYear = new TransactionExplorerDataDimension('Transaction Fiscal Year', TransactionExplorerDataDimensionType.DateTimeByFiscalYear, false, false); + public static readonly DateTimeByDayOfWeek = new TransactionExplorerDataDimension('Transaction Day of Week', TransactionExplorerDataDimensionType.DateTimeByDayOfWeek, false, false); + public static readonly DateTimeByDayOfMonth = new TransactionExplorerDataDimension('Transaction Day of Month', TransactionExplorerDataDimensionType.DateTimeByDayOfMonth, false, false); + public static readonly DateTimeByMonthOfYear = new TransactionExplorerDataDimension('Transaction Month of Year', TransactionExplorerDataDimensionType.DateTimeByMonthOfYear, false, false); + public static readonly DateTimeByQuarterOfYear = new TransactionExplorerDataDimension('Transaction Quarter of Year', TransactionExplorerDataDimensionType.DateTimeByQuarterOfYear, false, false); + public static readonly DateTimeByHourOfDay = new TransactionExplorerDataDimension('Transaction Hour of Day', TransactionExplorerDataDimensionType.DateTimeByHourOfDay, false, false); + public static readonly TimezoneOffset = new TransactionExplorerDataDimension('Transaction Timezone', TransactionExplorerDataDimensionType.TimezoneOffset, false, false); + public static readonly TransactionType = new TransactionExplorerDataDimension('Transaction Type', TransactionExplorerDataDimensionType.TransactionType, false, false); + public static readonly SourceAccount = new TransactionExplorerDataDimension('Source Account', TransactionExplorerDataDimensionType.SourceAccount, false, false); + public static readonly SourceAccountCategory = new TransactionExplorerDataDimension('Source Account Category', TransactionExplorerDataDimensionType.SourceAccountCategory, false, false); + public static readonly SourceAccountCurrency = new TransactionExplorerDataDimension('Source Account Currency', TransactionExplorerDataDimensionType.SourceAccountCurrency, false, false); + public static readonly DestinationAccount = new TransactionExplorerDataDimension('Destination Account', TransactionExplorerDataDimensionType.DestinationAccount, false, false); + public static readonly DestinationAccountCategory = new TransactionExplorerDataDimension('Destination Account Category', TransactionExplorerDataDimensionType.DestinationAccountCategory, false, false); + public static readonly DestinationAccountCurrency = new TransactionExplorerDataDimension('Destination Account Currency', TransactionExplorerDataDimensionType.DestinationAccountCurrency, false, false); + public static readonly PrimaryCategory = new TransactionExplorerDataDimension('Primary Category', TransactionExplorerDataDimensionType.PrimaryCategory, false, false); + public static readonly SecondaryCategory = new TransactionExplorerDataDimension('Secondary Category', TransactionExplorerDataDimensionType.SecondaryCategory, false, false); + public static readonly SourceAmount = new TransactionExplorerDataDimension('Amount', TransactionExplorerDataDimensionType.SourceAmount, false, false); + public static readonly DestinationAmount = new TransactionExplorerDataDimension('Transfer In Amount', TransactionExplorerDataDimensionType.DestinationAmount, false, false); + public static readonly SourceAmountRangeEqualFrequency = new TransactionExplorerDataDimension('Amount Range (Equal Frequency)', TransactionExplorerDataDimensionType.SourceAmountRangeEqualFrequency, true, false); + public static readonly SourceAmountRangeEqualWidth = new TransactionExplorerDataDimension('Amount Range (Equal Width)', TransactionExplorerDataDimensionType.SourceAmountRangeEqualWidth, true, false); + public static readonly SourceAmountRangeLogScale = new TransactionExplorerDataDimension('Amount Range (Log Scale)', TransactionExplorerDataDimensionType.SourceAmountRangeLogScale, true, false); + public static readonly SourceAmountRangeStandardDeviation = new TransactionExplorerDataDimension('Amount Range (Standard Deviation)', TransactionExplorerDataDimensionType.SourceAmountRangeStandardDeviation, true, false); + public static readonly SourceAmountRangeNaturalBreaks = new TransactionExplorerDataDimension('Amount Range (Natural Breaks)', TransactionExplorerDataDimensionType.SourceAmountRangeNaturalBreaks, true, false); + public static readonly DestinationAmountRangeEqualFrequency = new TransactionExplorerDataDimension('Transfer In Amount Range (Equal Frequency)', TransactionExplorerDataDimensionType.DestinationAmountRangeEqualFrequency, false, true); + public static readonly DestinationAmountRangeEqualWidth = new TransactionExplorerDataDimension('Transfer In Amount Range (Equal Width)', TransactionExplorerDataDimensionType.DestinationAmountRangeEqualWidth, false, true); + public static readonly DestinationAmountRangeLogScale = new TransactionExplorerDataDimension('Transfer In Amount Range (Log Scale)', TransactionExplorerDataDimensionType.DestinationAmountRangeLogScale, false, true); + public static readonly DestinationAmountRangeStandardDeviation = new TransactionExplorerDataDimension('Transfer In Amount Range (Standard Deviation)', TransactionExplorerDataDimensionType.DestinationAmountRangeStandardDeviation, false, true); + public static readonly DestinationAmountRangeNaturalBreaks = new TransactionExplorerDataDimension('Transfer In Amount Range (Natural Breaks)', TransactionExplorerDataDimensionType.DestinationAmountRangeNaturalBreaks, false, true); public static readonly CategoryDimensionDefault = TransactionExplorerDataDimension.Query; public static readonly SeriesDimensionDefault = TransactionExplorerDataDimension.None; public readonly name: string; public readonly value: TransactionExplorerDataDimensionType; + public readonly isSourceAmountRange: boolean; + public readonly isDestinationAmountRange: boolean; - private constructor(name: string, value: TransactionExplorerDataDimensionType) { + private constructor(name: string, value: TransactionExplorerDataDimensionType, isSourceAmountRange: boolean, isDestinationAmountRange: boolean) { this.name = name; this.value = value; + this.isSourceAmountRange = isSourceAmountRange; + this.isDestinationAmountRange = isDestinationAmountRange; TransactionExplorerDataDimension.allInstances.push(this); TransactionExplorerDataDimension.allInstancesByValue[value] = this; diff --git a/src/locales/de.json b/src/locales/de.json index c5f2a887..d3d41eae 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -1804,6 +1804,17 @@ "Source Account Currency": "Quellkonto-Währung", "Destination Account Category": "Zielkonto-Kategorie", "Destination Account Currency": "Zielkonto-Währung", + "Amount Range (Equal Frequency)": "Amount Range (Equal Frequency)", + "Amount Range (Equal Width)": "Amount Range (Equal Width)", + "Amount Range (Log Scale)": "Amount Range (Log Scale)", + "Amount Range (Standard Deviation)": "Amount Range (Standard Deviation)", + "Amount Range (Natural Breaks)": "Amount Range (Natural Breaks)", + "Transfer In Amount Range (Equal Frequency)": "Transfer In Amount Range (Equal Frequency)", + "Transfer In Amount Range (Equal Width)": "Transfer In Amount Range (Equal Width)", + "Transfer In Amount Range (Log Scale)": "Transfer In Amount Range (Log Scale)", + "Transfer In Amount Range (Standard Deviation)": "Transfer In Amount Range (Standard Deviation)", + "Transfer In Amount Range (Natural Breaks)": "Transfer In Amount Range (Natural Breaks)", + "Number of Amount Ranges": "Number of Amount Ranges", "Value Metric": "Wertmetrik", "Transaction Count": "Transaktionsanzahl", "Active Transaction Days": "Active Transaction Days", diff --git a/src/locales/en.json b/src/locales/en.json index 3fdb4002..35c65d6d 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1804,6 +1804,17 @@ "Source Account Currency": "Source Account Currency", "Destination Account Category": "Destination Account Category", "Destination Account Currency": "Destination Account Currency", + "Amount Range (Equal Frequency)": "Amount Range (Equal Frequency)", + "Amount Range (Equal Width)": "Amount Range (Equal Width)", + "Amount Range (Log Scale)": "Amount Range (Log Scale)", + "Amount Range (Standard Deviation)": "Amount Range (Standard Deviation)", + "Amount Range (Natural Breaks)": "Amount Range (Natural Breaks)", + "Transfer In Amount Range (Equal Frequency)": "Transfer In Amount Range (Equal Frequency)", + "Transfer In Amount Range (Equal Width)": "Transfer In Amount Range (Equal Width)", + "Transfer In Amount Range (Log Scale)": "Transfer In Amount Range (Log Scale)", + "Transfer In Amount Range (Standard Deviation)": "Transfer In Amount Range (Standard Deviation)", + "Transfer In Amount Range (Natural Breaks)": "Transfer In Amount Range (Natural Breaks)", + "Number of Amount Ranges": "Number of Amount Ranges", "Value Metric": "Value Metric", "Transaction Count": "Transaction Count", "Active Transaction Days": "Active Transaction Days", diff --git a/src/locales/es.json b/src/locales/es.json index 2279563b..e88e4e68 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -1804,6 +1804,17 @@ "Source Account Currency": "Moneda de la cuenta de origen", "Destination Account Category": "Categoría de la cuenta de destino", "Destination Account Currency": "Moneda de la cuenta de destino", + "Amount Range (Equal Frequency)": "Amount Range (Equal Frequency)", + "Amount Range (Equal Width)": "Amount Range (Equal Width)", + "Amount Range (Log Scale)": "Amount Range (Log Scale)", + "Amount Range (Standard Deviation)": "Amount Range (Standard Deviation)", + "Amount Range (Natural Breaks)": "Amount Range (Natural Breaks)", + "Transfer In Amount Range (Equal Frequency)": "Transfer In Amount Range (Equal Frequency)", + "Transfer In Amount Range (Equal Width)": "Transfer In Amount Range (Equal Width)", + "Transfer In Amount Range (Log Scale)": "Transfer In Amount Range (Log Scale)", + "Transfer In Amount Range (Standard Deviation)": "Transfer In Amount Range (Standard Deviation)", + "Transfer In Amount Range (Natural Breaks)": "Transfer In Amount Range (Natural Breaks)", + "Number of Amount Ranges": "Number of Amount Ranges", "Value Metric": "Métrica de valor", "Transaction Count": "Recuento de transacciones", "Active Transaction Days": "Active Transaction Days", diff --git a/src/locales/fr.json b/src/locales/fr.json index 38bb9aa1..986f161a 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -1804,6 +1804,17 @@ "Source Account Currency": "Source Account Currency", "Destination Account Category": "Destination Account Category", "Destination Account Currency": "Destination Account Currency", + "Amount Range (Equal Frequency)": "Amount Range (Equal Frequency)", + "Amount Range (Equal Width)": "Amount Range (Equal Width)", + "Amount Range (Log Scale)": "Amount Range (Log Scale)", + "Amount Range (Standard Deviation)": "Amount Range (Standard Deviation)", + "Amount Range (Natural Breaks)": "Amount Range (Natural Breaks)", + "Transfer In Amount Range (Equal Frequency)": "Transfer In Amount Range (Equal Frequency)", + "Transfer In Amount Range (Equal Width)": "Transfer In Amount Range (Equal Width)", + "Transfer In Amount Range (Log Scale)": "Transfer In Amount Range (Log Scale)", + "Transfer In Amount Range (Standard Deviation)": "Transfer In Amount Range (Standard Deviation)", + "Transfer In Amount Range (Natural Breaks)": "Transfer In Amount Range (Natural Breaks)", + "Number of Amount Ranges": "Number of Amount Ranges", "Value Metric": "Value Metric", "Transaction Count": "Transaction Count", "Active Transaction Days": "Active Transaction Days", diff --git a/src/locales/it.json b/src/locales/it.json index 3bda82f8..c90e9c24 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -1804,6 +1804,17 @@ "Source Account Currency": "Source Account Currency", "Destination Account Category": "Destination Account Category", "Destination Account Currency": "Destination Account Currency", + "Amount Range (Equal Frequency)": "Amount Range (Equal Frequency)", + "Amount Range (Equal Width)": "Amount Range (Equal Width)", + "Amount Range (Log Scale)": "Amount Range (Log Scale)", + "Amount Range (Standard Deviation)": "Amount Range (Standard Deviation)", + "Amount Range (Natural Breaks)": "Amount Range (Natural Breaks)", + "Transfer In Amount Range (Equal Frequency)": "Transfer In Amount Range (Equal Frequency)", + "Transfer In Amount Range (Equal Width)": "Transfer In Amount Range (Equal Width)", + "Transfer In Amount Range (Log Scale)": "Transfer In Amount Range (Log Scale)", + "Transfer In Amount Range (Standard Deviation)": "Transfer In Amount Range (Standard Deviation)", + "Transfer In Amount Range (Natural Breaks)": "Transfer In Amount Range (Natural Breaks)", + "Number of Amount Ranges": "Number of Amount Ranges", "Value Metric": "Value Metric", "Transaction Count": "Transaction Count", "Active Transaction Days": "Active Transaction Days", diff --git a/src/locales/ja.json b/src/locales/ja.json index aae5b171..df2230b8 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -1804,6 +1804,17 @@ "Source Account Currency": "Source Account Currency", "Destination Account Category": "Destination Account Category", "Destination Account Currency": "Destination Account Currency", + "Amount Range (Equal Frequency)": "Amount Range (Equal Frequency)", + "Amount Range (Equal Width)": "Amount Range (Equal Width)", + "Amount Range (Log Scale)": "Amount Range (Log Scale)", + "Amount Range (Standard Deviation)": "Amount Range (Standard Deviation)", + "Amount Range (Natural Breaks)": "Amount Range (Natural Breaks)", + "Transfer In Amount Range (Equal Frequency)": "Transfer In Amount Range (Equal Frequency)", + "Transfer In Amount Range (Equal Width)": "Transfer In Amount Range (Equal Width)", + "Transfer In Amount Range (Log Scale)": "Transfer In Amount Range (Log Scale)", + "Transfer In Amount Range (Standard Deviation)": "Transfer In Amount Range (Standard Deviation)", + "Transfer In Amount Range (Natural Breaks)": "Transfer In Amount Range (Natural Breaks)", + "Number of Amount Ranges": "Number of Amount Ranges", "Value Metric": "Value Metric", "Transaction Count": "Transaction Count", "Active Transaction Days": "Active Transaction Days", diff --git a/src/locales/kn.json b/src/locales/kn.json index 7d0c15f4..7088a689 100644 --- a/src/locales/kn.json +++ b/src/locales/kn.json @@ -1804,6 +1804,17 @@ "Source Account Currency": "Source Account Currency", "Destination Account Category": "Destination Account Category", "Destination Account Currency": "Destination Account Currency", + "Amount Range (Equal Frequency)": "Amount Range (Equal Frequency)", + "Amount Range (Equal Width)": "Amount Range (Equal Width)", + "Amount Range (Log Scale)": "Amount Range (Log Scale)", + "Amount Range (Standard Deviation)": "Amount Range (Standard Deviation)", + "Amount Range (Natural Breaks)": "Amount Range (Natural Breaks)", + "Transfer In Amount Range (Equal Frequency)": "Transfer In Amount Range (Equal Frequency)", + "Transfer In Amount Range (Equal Width)": "Transfer In Amount Range (Equal Width)", + "Transfer In Amount Range (Log Scale)": "Transfer In Amount Range (Log Scale)", + "Transfer In Amount Range (Standard Deviation)": "Transfer In Amount Range (Standard Deviation)", + "Transfer In Amount Range (Natural Breaks)": "Transfer In Amount Range (Natural Breaks)", + "Number of Amount Ranges": "Number of Amount Ranges", "Value Metric": "Value Metric", "Transaction Count": "Transaction Count", "Active Transaction Days": "Active Transaction Days", diff --git a/src/locales/ko.json b/src/locales/ko.json index 2c6f1bf5..c7875652 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -1804,6 +1804,17 @@ "Source Account Currency": "출처 계좌 통화", "Destination Account Category": "목적지 계좌 분류", "Destination Account Currency": "목적지 계좌 통화", + "Amount Range (Equal Frequency)": "Amount Range (Equal Frequency)", + "Amount Range (Equal Width)": "Amount Range (Equal Width)", + "Amount Range (Log Scale)": "Amount Range (Log Scale)", + "Amount Range (Standard Deviation)": "Amount Range (Standard Deviation)", + "Amount Range (Natural Breaks)": "Amount Range (Natural Breaks)", + "Transfer In Amount Range (Equal Frequency)": "Transfer In Amount Range (Equal Frequency)", + "Transfer In Amount Range (Equal Width)": "Transfer In Amount Range (Equal Width)", + "Transfer In Amount Range (Log Scale)": "Transfer In Amount Range (Log Scale)", + "Transfer In Amount Range (Standard Deviation)": "Transfer In Amount Range (Standard Deviation)", + "Transfer In Amount Range (Natural Breaks)": "Transfer In Amount Range (Natural Breaks)", + "Number of Amount Ranges": "Number of Amount Ranges", "Value Metric": "값 메트릭", "Transaction Count": "거래 수", "Active Transaction Days": "Active Transaction Days", diff --git a/src/locales/nl.json b/src/locales/nl.json index 9c232848..dbe8ae85 100644 --- a/src/locales/nl.json +++ b/src/locales/nl.json @@ -1804,6 +1804,17 @@ "Source Account Currency": "Source Account Currency", "Destination Account Category": "Destination Account Category", "Destination Account Currency": "Destination Account Currency", + "Amount Range (Equal Frequency)": "Amount Range (Equal Frequency)", + "Amount Range (Equal Width)": "Amount Range (Equal Width)", + "Amount Range (Log Scale)": "Amount Range (Log Scale)", + "Amount Range (Standard Deviation)": "Amount Range (Standard Deviation)", + "Amount Range (Natural Breaks)": "Amount Range (Natural Breaks)", + "Transfer In Amount Range (Equal Frequency)": "Transfer In Amount Range (Equal Frequency)", + "Transfer In Amount Range (Equal Width)": "Transfer In Amount Range (Equal Width)", + "Transfer In Amount Range (Log Scale)": "Transfer In Amount Range (Log Scale)", + "Transfer In Amount Range (Standard Deviation)": "Transfer In Amount Range (Standard Deviation)", + "Transfer In Amount Range (Natural Breaks)": "Transfer In Amount Range (Natural Breaks)", + "Number of Amount Ranges": "Number of Amount Ranges", "Value Metric": "Value Metric", "Transaction Count": "Transaction Count", "Active Transaction Days": "Active Transaction Days", diff --git a/src/locales/pt_BR.json b/src/locales/pt_BR.json index 98389739..b1d485fe 100644 --- a/src/locales/pt_BR.json +++ b/src/locales/pt_BR.json @@ -1804,6 +1804,17 @@ "Source Account Currency": "Moeda da Conta de Origem", "Destination Account Category": "Categoria da Conta de Destino", "Destination Account Currency": "Moeda da Conta de Destino", + "Amount Range (Equal Frequency)": "Amount Range (Equal Frequency)", + "Amount Range (Equal Width)": "Amount Range (Equal Width)", + "Amount Range (Log Scale)": "Amount Range (Log Scale)", + "Amount Range (Standard Deviation)": "Amount Range (Standard Deviation)", + "Amount Range (Natural Breaks)": "Amount Range (Natural Breaks)", + "Transfer In Amount Range (Equal Frequency)": "Transfer In Amount Range (Equal Frequency)", + "Transfer In Amount Range (Equal Width)": "Transfer In Amount Range (Equal Width)", + "Transfer In Amount Range (Log Scale)": "Transfer In Amount Range (Log Scale)", + "Transfer In Amount Range (Standard Deviation)": "Transfer In Amount Range (Standard Deviation)", + "Transfer In Amount Range (Natural Breaks)": "Transfer In Amount Range (Natural Breaks)", + "Number of Amount Ranges": "Number of Amount Ranges", "Value Metric": "Métrica de Valor", "Transaction Count": "Quantidade de Transações", "Active Transaction Days": "Active Transaction Days", diff --git a/src/locales/ru.json b/src/locales/ru.json index 4276af5b..fd92ebbe 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -1804,6 +1804,17 @@ "Source Account Currency": "Валюта исходного счёта", "Destination Account Category": "Категория целевого счёта", "Destination Account Currency": "Валюта целевого счёта", + "Amount Range (Equal Frequency)": "Amount Range (Equal Frequency)", + "Amount Range (Equal Width)": "Amount Range (Equal Width)", + "Amount Range (Log Scale)": "Amount Range (Log Scale)", + "Amount Range (Standard Deviation)": "Amount Range (Standard Deviation)", + "Amount Range (Natural Breaks)": "Amount Range (Natural Breaks)", + "Transfer In Amount Range (Equal Frequency)": "Transfer In Amount Range (Equal Frequency)", + "Transfer In Amount Range (Equal Width)": "Transfer In Amount Range (Equal Width)", + "Transfer In Amount Range (Log Scale)": "Transfer In Amount Range (Log Scale)", + "Transfer In Amount Range (Standard Deviation)": "Transfer In Amount Range (Standard Deviation)", + "Transfer In Amount Range (Natural Breaks)": "Transfer In Amount Range (Natural Breaks)", + "Number of Amount Ranges": "Number of Amount Ranges", "Value Metric": "Метрика значения", "Transaction Count": "Количество транзакций", "Active Transaction Days": "Active Transaction Days", diff --git a/src/locales/sl.json b/src/locales/sl.json index ce40b882..11f85970 100644 --- a/src/locales/sl.json +++ b/src/locales/sl.json @@ -1804,6 +1804,17 @@ "Source Account Currency": "Valuta izvornega računa", "Destination Account Category": "Kategorija ciljnega računa", "Destination Account Currency": "Valuta ciljnega računa", + "Amount Range (Equal Frequency)": "Amount Range (Equal Frequency)", + "Amount Range (Equal Width)": "Amount Range (Equal Width)", + "Amount Range (Log Scale)": "Amount Range (Log Scale)", + "Amount Range (Standard Deviation)": "Amount Range (Standard Deviation)", + "Amount Range (Natural Breaks)": "Amount Range (Natural Breaks)", + "Transfer In Amount Range (Equal Frequency)": "Transfer In Amount Range (Equal Frequency)", + "Transfer In Amount Range (Equal Width)": "Transfer In Amount Range (Equal Width)", + "Transfer In Amount Range (Log Scale)": "Transfer In Amount Range (Log Scale)", + "Transfer In Amount Range (Standard Deviation)": "Transfer In Amount Range (Standard Deviation)", + "Transfer In Amount Range (Natural Breaks)": "Transfer In Amount Range (Natural Breaks)", + "Number of Amount Ranges": "Number of Amount Ranges", "Value Metric": "Metrika vrednosti", "Transaction Count": "Število transakcij", "Active Transaction Days": "Active Transaction Days", diff --git a/src/locales/ta.json b/src/locales/ta.json index c6bb5d16..85c235e4 100644 --- a/src/locales/ta.json +++ b/src/locales/ta.json @@ -1804,6 +1804,17 @@ "Source Account Currency": "மூல கணக்கு நாணயம்", "Destination Account Category": "இலக்கு கணக்கு வகை", "Destination Account Currency": "இலக்கு கணக்கு நாணயம்", + "Amount Range (Equal Frequency)": "Amount Range (Equal Frequency)", + "Amount Range (Equal Width)": "Amount Range (Equal Width)", + "Amount Range (Log Scale)": "Amount Range (Log Scale)", + "Amount Range (Standard Deviation)": "Amount Range (Standard Deviation)", + "Amount Range (Natural Breaks)": "Amount Range (Natural Breaks)", + "Transfer In Amount Range (Equal Frequency)": "Transfer In Amount Range (Equal Frequency)", + "Transfer In Amount Range (Equal Width)": "Transfer In Amount Range (Equal Width)", + "Transfer In Amount Range (Log Scale)": "Transfer In Amount Range (Log Scale)", + "Transfer In Amount Range (Standard Deviation)": "Transfer In Amount Range (Standard Deviation)", + "Transfer In Amount Range (Natural Breaks)": "Transfer In Amount Range (Natural Breaks)", + "Number of Amount Ranges": "Number of Amount Ranges", "Value Metric": "மதிப்பு அளவீடு", "Transaction Count": "பரிவர்த்தனை எண்ணிக்கை", "Active Transaction Days": "Active Transaction Days", diff --git a/src/locales/th.json b/src/locales/th.json index 4a6ab5e7..e38cc052 100644 --- a/src/locales/th.json +++ b/src/locales/th.json @@ -1804,6 +1804,17 @@ "Source Account Currency": "Source Account Currency", "Destination Account Category": "Destination Account Category", "Destination Account Currency": "Destination Account Currency", + "Amount Range (Equal Frequency)": "Amount Range (Equal Frequency)", + "Amount Range (Equal Width)": "Amount Range (Equal Width)", + "Amount Range (Log Scale)": "Amount Range (Log Scale)", + "Amount Range (Standard Deviation)": "Amount Range (Standard Deviation)", + "Amount Range (Natural Breaks)": "Amount Range (Natural Breaks)", + "Transfer In Amount Range (Equal Frequency)": "Transfer In Amount Range (Equal Frequency)", + "Transfer In Amount Range (Equal Width)": "Transfer In Amount Range (Equal Width)", + "Transfer In Amount Range (Log Scale)": "Transfer In Amount Range (Log Scale)", + "Transfer In Amount Range (Standard Deviation)": "Transfer In Amount Range (Standard Deviation)", + "Transfer In Amount Range (Natural Breaks)": "Transfer In Amount Range (Natural Breaks)", + "Number of Amount Ranges": "Number of Amount Ranges", "Value Metric": "Value Metric", "Transaction Count": "Transaction Count", "Active Transaction Days": "Active Transaction Days", diff --git a/src/locales/tr.json b/src/locales/tr.json index f6a28420..d4833240 100644 --- a/src/locales/tr.json +++ b/src/locales/tr.json @@ -1804,6 +1804,17 @@ "Source Account Currency": "Source Account Currency", "Destination Account Category": "Destination Account Category", "Destination Account Currency": "Destination Account Currency", + "Amount Range (Equal Frequency)": "Amount Range (Equal Frequency)", + "Amount Range (Equal Width)": "Amount Range (Equal Width)", + "Amount Range (Log Scale)": "Amount Range (Log Scale)", + "Amount Range (Standard Deviation)": "Amount Range (Standard Deviation)", + "Amount Range (Natural Breaks)": "Amount Range (Natural Breaks)", + "Transfer In Amount Range (Equal Frequency)": "Transfer In Amount Range (Equal Frequency)", + "Transfer In Amount Range (Equal Width)": "Transfer In Amount Range (Equal Width)", + "Transfer In Amount Range (Log Scale)": "Transfer In Amount Range (Log Scale)", + "Transfer In Amount Range (Standard Deviation)": "Transfer In Amount Range (Standard Deviation)", + "Transfer In Amount Range (Natural Breaks)": "Transfer In Amount Range (Natural Breaks)", + "Number of Amount Ranges": "Number of Amount Ranges", "Value Metric": "Value Metric", "Transaction Count": "Transaction Count", "Active Transaction Days": "Active Transaction Days", diff --git a/src/locales/uk.json b/src/locales/uk.json index f4932375..f96f10e7 100644 --- a/src/locales/uk.json +++ b/src/locales/uk.json @@ -1804,6 +1804,17 @@ "Source Account Currency": "Source Account Currency", "Destination Account Category": "Destination Account Category", "Destination Account Currency": "Destination Account Currency", + "Amount Range (Equal Frequency)": "Amount Range (Equal Frequency)", + "Amount Range (Equal Width)": "Amount Range (Equal Width)", + "Amount Range (Log Scale)": "Amount Range (Log Scale)", + "Amount Range (Standard Deviation)": "Amount Range (Standard Deviation)", + "Amount Range (Natural Breaks)": "Amount Range (Natural Breaks)", + "Transfer In Amount Range (Equal Frequency)": "Transfer In Amount Range (Equal Frequency)", + "Transfer In Amount Range (Equal Width)": "Transfer In Amount Range (Equal Width)", + "Transfer In Amount Range (Log Scale)": "Transfer In Amount Range (Log Scale)", + "Transfer In Amount Range (Standard Deviation)": "Transfer In Amount Range (Standard Deviation)", + "Transfer In Amount Range (Natural Breaks)": "Transfer In Amount Range (Natural Breaks)", + "Number of Amount Ranges": "Number of Amount Ranges", "Value Metric": "Value Metric", "Transaction Count": "Transaction Count", "Active Transaction Days": "Active Transaction Days", diff --git a/src/locales/vi.json b/src/locales/vi.json index 05fd5ee3..a34d3a08 100644 --- a/src/locales/vi.json +++ b/src/locales/vi.json @@ -1804,6 +1804,17 @@ "Source Account Currency": "Source Account Currency", "Destination Account Category": "Destination Account Category", "Destination Account Currency": "Destination Account Currency", + "Amount Range (Equal Frequency)": "Amount Range (Equal Frequency)", + "Amount Range (Equal Width)": "Amount Range (Equal Width)", + "Amount Range (Log Scale)": "Amount Range (Log Scale)", + "Amount Range (Standard Deviation)": "Amount Range (Standard Deviation)", + "Amount Range (Natural Breaks)": "Amount Range (Natural Breaks)", + "Transfer In Amount Range (Equal Frequency)": "Transfer In Amount Range (Equal Frequency)", + "Transfer In Amount Range (Equal Width)": "Transfer In Amount Range (Equal Width)", + "Transfer In Amount Range (Log Scale)": "Transfer In Amount Range (Log Scale)", + "Transfer In Amount Range (Standard Deviation)": "Transfer In Amount Range (Standard Deviation)", + "Transfer In Amount Range (Natural Breaks)": "Transfer In Amount Range (Natural Breaks)", + "Number of Amount Ranges": "Number of Amount Ranges", "Value Metric": "Value Metric", "Transaction Count": "Transaction Count", "Active Transaction Days": "Active Transaction Days", diff --git a/src/locales/zh_Hans.json b/src/locales/zh_Hans.json index 81b503cc..9745c912 100644 --- a/src/locales/zh_Hans.json +++ b/src/locales/zh_Hans.json @@ -1804,6 +1804,17 @@ "Source Account Currency": "来源账户货币", "Destination Account Category": "目标账户分类", "Destination Account Currency": "目标账户货币", + "Amount Range (Equal Frequency)": "金额区间(等频)", + "Amount Range (Equal Width)": "金额区间(等宽)", + "Amount Range (Log Scale)": "金额区间(对数刻度)", + "Amount Range (Standard Deviation)": "金额区间(标准差)", + "Amount Range (Natural Breaks)": "金额区间(自然断点)", + "Transfer In Amount Range (Equal Frequency)": "转入金额区间(等频)", + "Transfer In Amount Range (Equal Width)": "转入金额区间(等宽)", + "Transfer In Amount Range (Log Scale)": "转入金额区间(对数刻度)", + "Transfer In Amount Range (Standard Deviation)": "转入金额区间(标准差)", + "Transfer In Amount Range (Natural Breaks)": "转入金额区间(自然断点)", + "Number of Amount Ranges": "金额区间数量", "Value Metric": "值类型", "Transaction Count": "交易数量", "Active Transaction Days": "活跃交易日", diff --git a/src/locales/zh_Hant.json b/src/locales/zh_Hant.json index 45a6f1c6..29371b3d 100644 --- a/src/locales/zh_Hant.json +++ b/src/locales/zh_Hant.json @@ -1804,6 +1804,17 @@ "Source Account Currency": "來源帳戶貨幣", "Destination Account Category": "目標帳戶分類", "Destination Account Currency": "目標帳戶貨幣", + "Amount Range (Equal Frequency)": "金額區間(等頻)", + "Amount Range (Equal Width)": "金額區間(等寬)", + "Amount Range (Log Scale)": "金額區間(對數刻度)", + "Amount Range (Standard Deviation)": "金額區間(標準差)", + "Amount Range (Natural Breaks)": "金額區間(自然斷點)", + "Transfer In Amount Range (Equal Frequency)": "轉入金額區間(等頻)", + "Transfer In Amount Range (Equal Width)": "轉入金額區間(等寬)", + "Transfer In Amount Range (Log Scale)": "轉入金額區間(對數刻度)", + "Transfer In Amount Range (Standard Deviation)": "转入金額區間(標準差)", + "Transfer In Amount Range (Natural Breaks)": "转入金額區間(自然斷點)", + "Number of Amount Ranges": "金額區間數量", "Value Metric": "值類型", "Transaction Count": "交易數量", "Active Transaction Days": "活躍交易日數", diff --git a/src/models/explorer.ts b/src/models/explorer.ts index 5fd8672b..f6120b41 100644 --- a/src/models/explorer.ts +++ b/src/models/explorer.ts @@ -72,6 +72,7 @@ export class InsightsExplorer implements InsightsExplorerInfoResponse { public chartType: TransactionExplorerChartTypeValue; public categoryDimension: TransactionExplorerDataDimensionType; public seriesDimension: TransactionExplorerDataDimensionType; + public amountRangeCount: number; public valueMetric: TransactionExplorerValueMetricType; public chartSortingType: number; @@ -87,11 +88,12 @@ export class InsightsExplorer implements InsightsExplorerInfoResponse { TransactionExplorerChartType.Default.value, TransactionExplorerDataDimension.CategoryDimensionDefault.value, TransactionExplorerDataDimension.SeriesDimensionDefault.value, + 5, TransactionExplorerValueMetric.Default.value, ChartSortingType.Default.type ); - private constructor(id: string, name: string, displayOrder: number, hidden: boolean, queries: TransactionExplorerQuery[], timezoneUsedForDateRange: number, datatableQuerySource: string, countPerPage: number, chartType: TransactionExplorerChartTypeValue, categoryDimension: TransactionExplorerDataDimensionType, seriesDimension: TransactionExplorerDataDimensionType, valueMetric: TransactionExplorerValueMetricType, chartSortingType: number) { + private constructor(id: string, name: string, displayOrder: number, hidden: boolean, queries: TransactionExplorerQuery[], timezoneUsedForDateRange: number, datatableQuerySource: string, countPerPage: number, chartType: TransactionExplorerChartTypeValue, categoryDimension: TransactionExplorerDataDimensionType, seriesDimension: TransactionExplorerDataDimensionType, amountRangeCount: number, valueMetric: TransactionExplorerValueMetricType, chartSortingType: number) { this.id = id; this.name = name; this.displayOrder = displayOrder; @@ -103,6 +105,7 @@ export class InsightsExplorer implements InsightsExplorerInfoResponse { this.chartType = chartType; this.categoryDimension = categoryDimension; this.seriesDimension = seriesDimension; + this.amountRangeCount = amountRangeCount; this.valueMetric = valueMetric; this.chartSortingType = chartSortingType; } @@ -116,6 +119,7 @@ export class InsightsExplorer implements InsightsExplorerInfoResponse { chartType: this.chartType, categoryDimension: this.categoryDimension, seriesDimension: this.seriesDimension, + amountRangeCount: this.amountRangeCount, valueMetric: this.valueMetric, chartSortingType: this.chartSortingType }; @@ -147,6 +151,7 @@ export class InsightsExplorer implements InsightsExplorerInfoResponse { let chartType = InsightsExplorer.Default.chartType; let categoryDimension = InsightsExplorer.Default.categoryDimension; let seriesDimension = InsightsExplorer.Default.seriesDimension; + let amountRangeCount = InsightsExplorer.Default.amountRangeCount; let valueMetric = InsightsExplorer.Default.valueMetric; let chartSortingType = InsightsExplorer.Default.chartSortingType; let hasDatatableQuerySource = false; @@ -176,6 +181,10 @@ export class InsightsExplorer implements InsightsExplorerInfoResponse { seriesDimension = data['seriesDimension'] as TransactionExplorerDataDimensionType; } + if (typeof data['amountRangeCount'] === 'number') { + amountRangeCount = data['amountRangeCount'] as number; + } + if (typeof data['valueMetric'] === 'string') { valueMetric = data['valueMetric'] as TransactionExplorerValueMetricType; } @@ -217,6 +226,7 @@ export class InsightsExplorer implements InsightsExplorerInfoResponse { chartType, categoryDimension, seriesDimension, + amountRangeCount, valueMetric, chartSortingType ); @@ -235,6 +245,7 @@ export class InsightsExplorer implements InsightsExplorerInfoResponse { InsightsExplorer.Default.chartType, InsightsExplorer.Default.categoryDimension, InsightsExplorer.Default.seriesDimension, + InsightsExplorer.Default.amountRangeCount, InsightsExplorer.Default.valueMetric, InsightsExplorer.Default.chartSortingType ); diff --git a/src/stores/explorer.ts b/src/stores/explorer.ts index 4cacd5c7..3910a875 100644 --- a/src/stores/explorer.ts +++ b/src/stores/explorer.ts @@ -46,6 +46,7 @@ import { getObjectOwnFieldCount } from '@/lib/common.ts'; import { + mean, median, percentile, sumMaxN, @@ -127,6 +128,19 @@ export interface CategoriedTransactionExplorerDataItem extends SeriesInfo { value: number; } +export interface AmountRanges { + categorySourceAmountRanges?: number[]; + categoryDestinationAmountRanges?: number[]; + seriesSourceAmountRanges?: number[]; + seriesDestinationAmountRanges?: number[]; +} + +export interface TransactionInsightDataItemInQuery { + queryIndex: number; + queryName: string; + transaction: TransactionInsightDataItem; +} + export interface InsightsExplorerTransactionStatisticData { totalCount: number; totalAmount: number; @@ -180,7 +194,180 @@ export const useExplorersStore = defineStore('explorers', () => { }; } - function getDataCategoryInfo(timezoneUsedForDateRange: number, dimension: TransactionExplorerDataDimension, queryName: string, queryIndex: number, transaction: TransactionInsightDataItem): CategoriedInfo { + function calculateAmountRanges(sortedAmounts: number[], dimension: TransactionExplorerDataDimension, rangeCount: number): number[] { + const result: number[] = []; + + if (sortedAmounts.length < 1 || rangeCount <= 0) { + return result; + } + + const minAmount = sortedAmounts[0] as number; + const maxAmount = sortedAmounts[sortedAmounts.length - 1] as number; + rangeCount = Math.min(rangeCount, sortedAmounts.length); + + // [min1, max1), [min2, max2), ..., [minN, maxN] + if (dimension === TransactionExplorerDataDimension.SourceAmountRangeEqualFrequency + || dimension === TransactionExplorerDataDimension.DestinationAmountRangeEqualFrequency) { + for (let i = 0; i < rangeCount; i++) { + result.push(sortedAmounts[Math.floor(i * (sortedAmounts.length - 1) / rangeCount)] as number); + } + result.push(maxAmount); + } else if (dimension === TransactionExplorerDataDimension.SourceAmountRangeEqualWidth + || dimension === TransactionExplorerDataDimension.DestinationAmountRangeEqualWidth) { + if (minAmount === maxAmount) { + return [minAmount, maxAmount]; + } + + const width: number = (maxAmount - minAmount) / rangeCount; + + for (let i = 0; i < rangeCount; i++) { + result.push(minAmount + i * width); + } + result.push(maxAmount); + } else if (dimension === TransactionExplorerDataDimension.SourceAmountRangeLogScale + || dimension === TransactionExplorerDataDimension.DestinationAmountRangeLogScale) { + const epsilon: number = 1e-9; + + const transform = (x: number): number => { + if (x === 0) { + return 0; + } + + return Math.sign(x) * Math.log(Math.abs(x) + epsilon); + }; + + const inverse = (y: number): number => { + if (y === 0) { + return 0; + } + + return Math.sign(y) * (Math.exp(Math.abs(y)) - epsilon); + }; + + const transformed = sortedAmounts.map(transform).sort((a, b) => a - b); + + const tMin: number = transformed[0] as number; + const tMax: number = transformed[transformed.length - 1] as number; + + if (tMin === tMax) { + return [minAmount, maxAmount]; + } + + const width: number = (tMax - tMin) / rangeCount; + + result.push(minAmount); + for (let i = 1; i < rangeCount; i++) { + result.push(inverse(tMin + i * width)); + } + result.push(maxAmount); + } else if (dimension === TransactionExplorerDataDimension.SourceAmountRangeStandardDeviation + || dimension === TransactionExplorerDataDimension.DestinationAmountRangeStandardDeviation) { + if (minAmount === maxAmount) { + return [minAmount, maxAmount]; + } + + const averageAmountForVarianceCalculation: number = mean(sortedAmounts, item => item) / AMOUNT_FACTOR; + const { standardDeviation } = varianceAndStandardDeviation(sortedAmounts, averageAmountForVarianceCalculation, item => item / AMOUNT_FACTOR); + + if (standardDeviation === 0) { + return [minAmount, maxAmount]; + } + + const rawBreaks: number[] = []; + const halfCount = Math.floor(rangeCount / 2); + + if (rangeCount % 2 === 1) { + for (let i = -halfCount; i <= halfCount; i++) { + rawBreaks.push((averageAmountForVarianceCalculation + i * standardDeviation) * AMOUNT_FACTOR); + } + } else { + for (let i = -halfCount; i <= halfCount; i++) { + if (i === 0) { + continue; + } + rawBreaks.push((averageAmountForVarianceCalculation + (i - 0.5) * standardDeviation) * AMOUNT_FACTOR); + } + rawBreaks.sort((a, b) => a - b); + } + + const clipped = rawBreaks.map((v) => Math.max(minAmount, Math.min(maxAmount, v))) + .filter((v, i, arr) => i === 0 || v !== arr[i - 1]); + + clipped[0] = minAmount; + + if (clipped[clipped.length - 1] !== maxAmount) { + clipped.push(maxAmount); + } + + return clipped; + } else if (dimension === TransactionExplorerDataDimension.SourceAmountRangeNaturalBreaks + || dimension === TransactionExplorerDataDimension.DestinationAmountRangeNaturalBreaks) { + if (minAmount === maxAmount) { + return [minAmount, maxAmount]; + } + + const n = sortedAmounts.length; + const k = Math.min(rangeCount, n); + + const lowerClassLimits: number[][] = Array.from({ length: n + 1 }, () => new Array(k + 1).fill(0)); + const varianceCombinations: number[][] = Array.from({ length: n + 1 }, () => new Array(k + 1).fill(Infinity)); + + for (let i = 1; i <= k; i++) { + lowerClassLimits[1]![i] = 1; + varianceCombinations[1]![i] = 0; + } + + for (let l = 2; l <= n; l++) { + let sumZ = 0; + let sumZ2 = 0; + + for (let m = 1; m <= l; m++) { + const val = sortedAmounts[l - m] as number; + sumZ += val; + sumZ2 += val * val; + + const variance = sumZ2 - (sumZ * sumZ) / m; + + if (m === l) { + for (let j = 1; j <= k; j++) { + if (variance < varianceCombinations[l]![j]!) { + lowerClassLimits[l]![j] = 1; + varianceCombinations[l]![j] = variance; + } + } + } else { + for (let j = 2; j <= k; j++) { + const combined = varianceCombinations[l - m]![j - 1]! + variance; + if (combined < varianceCombinations[l]![j]!) { + lowerClassLimits[l]![j] = l - m + 1; + varianceCombinations[l]![j] = combined; + } + } + } + } + } + + const breaks: number[] = new Array(k + 1); + breaks[k] = maxAmount; + + let currentK = k; + let currentIdx = n; + + while (currentK >= 2) { + const lowerIdx = lowerClassLimits[currentIdx]![currentK]!; + breaks[currentK - 1] = sortedAmounts[lowerIdx - 1] as number; + currentIdx = lowerIdx - 1; + currentK--; + } + + breaks[0] = minAmount; + return breaks; + } + + return result; + } + + function getDataCategoryInfo(timezoneUsedForDateRange: number, dimension: TransactionExplorerDataDimension, sourceAmountRanges: number[] | undefined, destinationAmountRanges: number[] | undefined, queryName: string, queryIndex: number, transaction: TransactionInsightDataItem): CategoriedInfo { const defaultCurrency = userStore.currentUserDefaultCurrency; let transactionTimeUtfOffset: number | undefined = undefined; @@ -468,6 +655,91 @@ export const useExplorersStore = defineStore('explorers', () => { categoryIdType: TransactionExplorerDimensionType.Amount, categoryDisplayOrders: [amountInDefaultCurrency] }; + } else if (dimension.isSourceAmountRange || dimension.isDestinationAmountRange) { + const isSourceAmount = dimension.isSourceAmountRange; + + if (dimension.isDestinationAmountRange && transaction.type !== TransactionType.Transfer) { + return { + categoryName: 'None', + categoryNameNeedI18n: true, + categoryId: 'none', + categoryIdType: TransactionExplorerDimensionType.Other, + categoryDisplayOrders: [Number.MAX_SAFE_INTEGER] + }; + } + + const amount = dimension.isSourceAmountRange ? transaction.sourceAmount : transaction.destinationAmount; + const account = dimension.isSourceAmountRange ? transaction.sourceAccount : transaction.destinationAccount; + let amountInDefaultCurrency: number = amount; + + if (!account) { + return { + categoryName: 'Unknown', + categoryNameNeedI18n: true, + categoryId: 'unknown', + categoryIdType: TransactionExplorerDimensionType.Other, + categoryDisplayOrders: [Number.MAX_SAFE_INTEGER] + }; + } + + if (account.currency !== defaultCurrency) { + const exchangedAmount = exchangeRatesStore.getExchangedAmount(amount, account.currency, defaultCurrency); + + if (isNumber(exchangedAmount)) { + amountInDefaultCurrency = Math.trunc(exchangedAmount); + } else { + return { + categoryName: 'Unknown', + categoryNameNeedI18n: true, + categoryId: 'unknown', + categoryIdType: TransactionExplorerDimensionType.Other, + categoryDisplayOrders: [Number.MAX_SAFE_INTEGER] + }; + } + } + + const amountRanges: number[] = isSourceAmount ? (sourceAmountRanges ?? []) : (destinationAmountRanges ?? []); + let matchAmountRangeMin: number | undefined = undefined; + let matchAmountRangeMax: number | undefined = undefined; + let matchAmountRangeIndex: number | undefined = undefined; + + for (let i = 1; i < amountRanges.length; i++) { + const amountRangeMin = amountRanges[i - 1] as number; + const amountRangeMax = amountRanges[i] as number; + + if (amountInDefaultCurrency < amountRangeMin) { + continue; + } + + if (amountInDefaultCurrency > amountRangeMax) { + continue; + } + + if (i < amountRanges.length - 1 && amountInDefaultCurrency === amountRangeMax) { + continue; + } + + matchAmountRangeMin = amountRangeMin; + matchAmountRangeMax = amountRangeMax; + matchAmountRangeIndex = i - 1; + } + + if (isNumber(matchAmountRangeMin) && isNumber(matchAmountRangeMax) && isNumber(matchAmountRangeIndex)) { + return { + categoryName: `${matchAmountRangeMin.toString(10)}|${matchAmountRangeMax.toString(10)}`, + categoryId: matchAmountRangeIndex.toString(10), + categoryIdType: TransactionExplorerDimensionType.Other, + categoryDisplayOrders: [matchAmountRangeIndex] + }; + } else { + return { + categoryName: 'Other', + categoryNameNeedI18n: true, + categoryId: 'other', + categoryIdType: TransactionExplorerDimensionType.Other, + categoryDisplayOrders: [Number.MAX_SAFE_INTEGER] + }; + } } else { return { categoryName: '', @@ -478,8 +750,37 @@ export const useExplorersStore = defineStore('explorers', () => { } } - function addTransactionToCategoriedDataMap(timezoneUsedForDateRange: number, categoriedDataMap: Record, categoryDimension: TransactionExplorerDataDimension, seriesDemension: TransactionExplorerDataDimension, queryName: string, queryIndex: number, transaction: TransactionInsightDataItem): void { - const categoriedInfo = getDataCategoryInfo(timezoneUsedForDateRange, categoryDimension, queryName, queryIndex, transaction); + function addTransactionToFilteredList(filteredTransactions: TransactionInsightDataItemInQuery[], filteredTransactionSourceAmountsInDefaultCurrency: number[], filteredTransactionDestinationAmountsInDefaultCurrency: number[], defaultCurrency: string, queryName: string, queryIndex: number, transaction: TransactionInsightDataItem): void { + filteredTransactions.push({ + queryIndex: queryIndex, + queryName: queryName, + transaction: transaction + }); + + let sourceAmountInDefaultCurrency: number | undefined = transaction.sourceAmount; + let destinationAmountInDefaultCurrency: number | undefined = transaction.type === TransactionType.Transfer && transaction.destinationAccount ? transaction.destinationAmount : undefined; + + if (transaction.sourceAccount.currency !== defaultCurrency) { + const amount = exchangeRatesStore.getExchangedAmount(transaction.sourceAmount, transaction.sourceAccount.currency, defaultCurrency); + sourceAmountInDefaultCurrency = isNumber(amount) ? Math.trunc(amount) : undefined; + } + + if (transaction.type === TransactionType.Transfer && transaction.destinationAccount && transaction.destinationAccount.currency !== defaultCurrency) { + const amount = exchangeRatesStore.getExchangedAmount(transaction.destinationAmount, transaction.destinationAccount.currency, defaultCurrency); + destinationAmountInDefaultCurrency = isNumber(amount) ? Math.trunc(amount) : undefined; + } + + if (isNumber(sourceAmountInDefaultCurrency)) { + filteredTransactionSourceAmountsInDefaultCurrency.push(sourceAmountInDefaultCurrency); + } + + if (isNumber(destinationAmountInDefaultCurrency)) { + filteredTransactionDestinationAmountsInDefaultCurrency.push(destinationAmountInDefaultCurrency); + } + } + + function addTransactionToCategoriedDataMap(timezoneUsedForDateRange: number, categoriedDataMap: Record, categoryDimension: TransactionExplorerDataDimension, seriesDemension: TransactionExplorerDataDimension, allAmountRanges: AmountRanges, queryName: string, queryIndex: number, transaction: TransactionInsightDataItem): void { + const categoriedInfo = getDataCategoryInfo(timezoneUsedForDateRange, categoryDimension, allAmountRanges.categorySourceAmountRanges, allAmountRanges.categoryDestinationAmountRanges, queryName, queryIndex, transaction); let categoriedData = categoriedDataMap[categoriedInfo.categoryId]; if (!categoriedData) { @@ -495,7 +796,7 @@ export const useExplorersStore = defineStore('explorers', () => { categoriedDataMap[categoriedInfo.categoryId] = categoriedData; } - const seriesInfo = getDataCategoryInfo(timezoneUsedForDateRange, seriesDemension, queryName, queryIndex, transaction); + const seriesInfo = getDataCategoryInfo(timezoneUsedForDateRange, seriesDemension, allAmountRanges.seriesSourceAmountRanges, allAmountRanges.seriesDestinationAmountRanges, queryName, queryIndex, transaction); let seriesData = categoriedData.trasactions[seriesInfo.categoryId]; if (!seriesData) { @@ -514,6 +815,37 @@ export const useExplorersStore = defineStore('explorers', () => { seriesData.trasactions.push(transaction); } + function buildAllAmountRanges(categoryDimension: TransactionExplorerDataDimension, seriesDimension: TransactionExplorerDataDimension, filteredTransactionSourceAmountsInDefaultCurrency: number[], filteredTransactionDestinationAmountsInDefaultCurrency: number[], rangeCount: number): AmountRanges { + const allAmountRanges: AmountRanges = {}; + + if (categoryDimension.isSourceAmountRange || seriesDimension.isSourceAmountRange) { + filteredTransactionSourceAmountsInDefaultCurrency.sort((a, b) => a - b); + const sorteUniqueAmounts = filteredTransactionSourceAmountsInDefaultCurrency.filter((v, i, a) => i === 0 || v !== a[i - 1]); + + if (categoryDimension.isSourceAmountRange) { + allAmountRanges.categorySourceAmountRanges = calculateAmountRanges(sorteUniqueAmounts, categoryDimension, rangeCount); + } + + if (seriesDimension.isSourceAmountRange) { + allAmountRanges.seriesSourceAmountRanges = calculateAmountRanges(sorteUniqueAmounts, seriesDimension, rangeCount); + } + } + + if (categoryDimension.isDestinationAmountRange || seriesDimension.isDestinationAmountRange) { + filteredTransactionDestinationAmountsInDefaultCurrency.sort((a, b) => a - b); + const sorteUniqueAmounts = filteredTransactionDestinationAmountsInDefaultCurrency.filter((v, i, a) => i === 0 || v !== a[i - 1]); + if (categoryDimension.isDestinationAmountRange) { + allAmountRanges.categoryDestinationAmountRanges = calculateAmountRanges(sorteUniqueAmounts, categoryDimension, rangeCount); + } + + if (seriesDimension.isDestinationAmountRange) { + allAmountRanges.seriesDestinationAmountRanges = calculateAmountRanges(sorteUniqueAmounts, seriesDimension, rangeCount); + } + } + + return allAmountRanges; + } + function loadInsightsExplorerList(explorers: InsightsExplorerBasicInfo[]): void { allInsightsExplorerBasicInfos.value = explorers; allInsightsExplorerBasicInfosMap.value = {}; @@ -650,6 +982,15 @@ export const useExplorersStore = defineStore('explorers', () => { return result; }); + const isUsingAmountRange = computed(() => { + const chartType = TransactionExplorerChartType.valueOf(currentInsightsExplorer.value.chartType); + const categoryDimension = TransactionExplorerDataDimension.valueOf(currentInsightsExplorer.value.categoryDimension); + const seriesDimension = chartType?.seriesDimensionRequired ? TransactionExplorerDataDimension.valueOf(currentInsightsExplorer.value.seriesDimension) : TransactionExplorerDataDimension.SeriesDimensionDefault; + return categoryDimension?.isSourceAmountRange || seriesDimension?.isSourceAmountRange + || categoryDimension?.isDestinationAmountRange || seriesDimension?.isDestinationAmountRange + || false; + }); + const filteredTransactionsInDataTable = computed(() => { if (!allTransactions.value || allTransactions.value.length < 1) { return []; @@ -795,11 +1136,14 @@ export const useExplorersStore = defineStore('explorers', () => { return {}; } - const categoriedDataMap: Record = {}; + const defaultCurrency = userStore.currentUserDefaultCurrency; + const filteredTransactions: TransactionInsightDataItemInQuery[] = []; + const filteredTransactionSourceAmountsInDefaultCurrency: number[] = []; + const filteredTransactionDestinationAmountsInDefaultCurrency: number[] = []; for (const transaction of allTransactions.value) { if (!currentInsightsExplorer.value.queries || currentInsightsExplorer.value.queries.length < 1) { - addTransactionToCategoriedDataMap(currentInsightsExplorer.value.timezoneUsedForDateRange, categoriedDataMap, categoryDimension, seriesDimension, '', 0, transaction); + addTransactionToFilteredList(filteredTransactions, filteredTransactionSourceAmountsInDefaultCurrency, filteredTransactionDestinationAmountsInDefaultCurrency, defaultCurrency, '', 0, transaction); continue; } @@ -807,7 +1151,7 @@ export const useExplorersStore = defineStore('explorers', () => { for (const [query, index] of itemAndIndex(currentInsightsExplorer.value.queries)) { if (query.match(transaction, matchContext)) { - addTransactionToCategoriedDataMap(currentInsightsExplorer.value.timezoneUsedForDateRange, categoriedDataMap, categoryDimension, seriesDimension, query.name, index, transaction); + addTransactionToFilteredList(filteredTransactions, filteredTransactionSourceAmountsInDefaultCurrency, filteredTransactionDestinationAmountsInDefaultCurrency, defaultCurrency, query.name, index, transaction); if (categoryDimension !== TransactionExplorerDataDimension.Query) { break; @@ -816,6 +1160,13 @@ export const useExplorersStore = defineStore('explorers', () => { } } + const categoriedDataMap: Record = {}; + const allAmountRanges: AmountRanges = buildAllAmountRanges(categoryDimension, seriesDimension, filteredTransactionSourceAmountsInDefaultCurrency, filteredTransactionDestinationAmountsInDefaultCurrency, currentInsightsExplorer.value.amountRangeCount); + + for (const item of filteredTransactions) { + addTransactionToCategoriedDataMap(currentInsightsExplorer.value.timezoneUsedForDateRange, categoriedDataMap, categoryDimension, seriesDimension, allAmountRanges, item.queryName, item.queryIndex, item.transaction); + } + return categoriedDataMap; }); @@ -1523,6 +1874,7 @@ export const useExplorersStore = defineStore('explorers', () => { currentInsightsExplorer, insightsExplorerListStateInvalid, // computed + isUsingAmountRange, filteredTransactionsInDataTable, filteredTransactionsInDataTableStatistic, categoriedTransactionExplorerData, diff --git a/src/views/desktop/insights/tabs/ExplorerChartTab.vue b/src/views/desktop/insights/tabs/ExplorerChartTab.vue index 5a77e7fc..38d90edb 100644 --- a/src/views/desktop/insights/tabs/ExplorerChartTab.vue +++ b/src/views/desktop/insights/tabs/ExplorerChartTab.vue @@ -46,6 +46,18 @@ + ('axisChart'); +const numeralSystem = computed(() => getCurrentNumeralSystemType()); const defaultCurrency = computed(() => userStore.currentUserDefaultCurrency); const allTransactionExplorerDataDimensions = computed(() => getAllTransactionExplorerDataDimensions()); @@ -301,6 +315,17 @@ const allTransactionExplorerChartSortingTypes = computed(( const currentTransactionExplorerCategoryDimensionName = computed(() => findNameByValue(allTransactionExplorerDataDimensions.value, currentExplorer.value.categoryDimension) ?? tt('Unknown')); const currentExplorer = computed(() => explorersStore.currentInsightsExplorer); +const isUsingAmountRange = computed(() => explorersStore.isUsingAmountRange); + +const allAmountRangeCounts = computed(() => { + const pageCounts: NameNumeralValue[] = []; + + for (let i = 3; i <= 20; i++) { + pageCounts.push({ value: i, name: numeralSystem.value.replaceWesternArabicDigitsToLocalizedDigits(i.toString()) }); + } + + return pageCounts; +}); const categoryDimensionTransactionExplorerData = computed(() => { if (currentExplorer.value.chartType !== TransactionExplorerChartType.Pie.value && currentExplorer.value.chartType !== TransactionExplorerChartType.Radar.value) { @@ -573,22 +598,23 @@ function getCategoriedDataDisplayName(info: CategoriedInfo | SeriesInfo): string let needI18n: boolean | undefined = false; let i18nParameters: Record | undefined = undefined; let dimessionType: TransactionExplorerDimensionType = TransactionExplorerDimensionType.Other; - let dimession: TransactionExplorerDataDimensionType = TransactionExplorerDataDimension.None.value; + let dimessionValue: TransactionExplorerDataDimensionType = TransactionExplorerDataDimension.None.value; if ('categoryName' in info) { name = info.categoryName; needI18n = info.categoryNameNeedI18n; i18nParameters = info.categoryNameI18nParameters; dimessionType = info.categoryIdType; - dimession = currentExplorer.value.categoryDimension; + dimessionValue = currentExplorer.value.categoryDimension; } else if ('seriesName' in info) { name = info.seriesName; needI18n = info.seriesNameNeedI18n; i18nParameters = info.seriesNameI18nParameters; dimessionType = info.seriesIdType; - dimession = currentExplorer.value.seriesDimension; + dimessionValue = currentExplorer.value.seriesDimension; } + const dimession = TransactionExplorerDataDimension.valueOf(dimessionValue); let displayName: string = name; // convert the name to i18n if needed @@ -599,38 +625,38 @@ function getCategoriedDataDisplayName(info: CategoriedInfo | SeriesInfo): string } // convert the name to formatted date time if needed - if (dimession === TransactionExplorerDataDimension.DateTime.value) { + if (dimession === TransactionExplorerDataDimension.DateTime) { const dateTime = parseDateTimeFromString(name, dimessionType); displayName = dateTime ? formatDateTimeToShortDateTime(dateTime) : tt('Unknown'); - } else if (dimession === TransactionExplorerDataDimension.DateTimeByYearMonthDay.value) { + } else if (dimession === TransactionExplorerDataDimension.DateTimeByYearMonthDay) { const dateTime = parseDateTimeFromString(name, dimessionType); displayName = dateTime ? formatDateTimeToShortDate(dateTime) : tt('Unknown'); - } else if (dimession === TransactionExplorerDataDimension.DateTimeByYearMonth.value) { + } else if (dimession === TransactionExplorerDataDimension.DateTimeByYearMonth) { const dateTime = parseDateTimeFromString(name, dimessionType); displayName = dateTime ? formatDateTimeToGregorianLikeShortYearMonth(dateTime) : tt('Unknown'); - } else if (dimession === TransactionExplorerDataDimension.DateTimeByYearQuarter.value) { + } else if (dimession === TransactionExplorerDataDimension.DateTimeByYearQuarter) { const parts = name.split('-'); const year = parts.length === 2 ? parts[0] : ''; const quarter = parts.length === 2 ? parseInt(parts[1] as string) : 0; const quarterLastMonth = quarter * 3; const dateTime = year && quarterLastMonth ? parseDateTimeFromString(`${year}-${quarterLastMonth.toString(10).padStart(2, NumeralSystem.WesternArabicNumerals.digitZero)}`, TransactionExplorerDimensionType.YearMonth) : undefined; displayName = dateTime ? formatDateTimeToGregorianLikeYearQuarter(dateTime) : tt('Unknown'); - } else if (dimession === TransactionExplorerDataDimension.DateTimeByYear.value) { + } else if (dimession === TransactionExplorerDataDimension.DateTimeByYear) { const dateTime = parseDateTimeFromString(name, dimessionType); displayName = dateTime ? formatDateTimeToGregorianLikeShortYear(dateTime) : tt('Unknown'); - } else if (dimession === TransactionExplorerDataDimension.DateTimeByFiscalYear.value) { + } else if (dimession === TransactionExplorerDataDimension.DateTimeByFiscalYear) { displayName = formatGregorianYearToGregorianLikeFiscalYear(parseInt(name)); - } else if (dimession === TransactionExplorerDataDimension.DateTimeByDayOfWeek.value) { + } else if (dimession === TransactionExplorerDataDimension.DateTimeByDayOfWeek) { const weekDay = WeekDay.parse(name); displayName = weekDay ? getWeekdayLongName(weekDay) : tt('Unknown'); - } else if (dimession === TransactionExplorerDataDimension.DateTimeByDayOfMonth.value) { + } else if (dimession === TransactionExplorerDataDimension.DateTimeByDayOfMonth) { displayName = getMonthdayShortName(parseInt(name)); - } else if (dimession === TransactionExplorerDataDimension.DateTimeByMonthOfYear.value) { + } else if (dimession === TransactionExplorerDataDimension.DateTimeByMonthOfYear) { const month = Month.valueOf(parseInt(name)); displayName = month ? getMonthLongName(month.name) : tt('Unknown'); - } else if (dimession === TransactionExplorerDataDimension.DateTimeByQuarterOfYear.value) { + } else if (dimession === TransactionExplorerDataDimension.DateTimeByQuarterOfYear) { displayName = getQuarterName(parseInt(name)); - } else if (dimession === TransactionExplorerDataDimension.DateTimeByHourOfDay.value) { + } else if (dimession === TransactionExplorerDataDimension.DateTimeByHourOfDay) { const dateTime = getCurrentDateTime().set({ hour: parseInt(name), minute: 0, @@ -638,19 +664,29 @@ function getCategoriedDataDisplayName(info: CategoriedInfo | SeriesInfo): string millisecond: 0 }); displayName = formatDateTimeToShortTime(dateTime); - } else if (dimession === TransactionExplorerDataDimension.SourceAccountCurrency.value || dimession === TransactionExplorerDataDimension.DestinationAccountCurrency.value) { + } else if (dimession === TransactionExplorerDataDimension.SourceAccountCurrency || dimession === TransactionExplorerDataDimension.DestinationAccountCurrency) { if (!needI18n) { displayName = getCurrencyName(name); } } - if (dimession === TransactionExplorerDataDimension.SourceAmount.value - || dimession === TransactionExplorerDataDimension.DestinationAmount.value) { + if (dimession === TransactionExplorerDataDimension.SourceAmount + || dimession === TransactionExplorerDataDimension.DestinationAmount) { if (name !== '' && name !== 'none' && Number.isFinite(parseInt(name))) { displayName = formatAmountToLocalizedNumerals(parseInt(name), defaultCurrency.value); } } + if (dimession?.isSourceAmountRange || dimession?.isDestinationAmountRange) { + const rangeParts = name.split('|'); + + if (rangeParts && rangeParts.length === 2 && Number.isFinite(parseInt(rangeParts[0] as string)) && Number.isFinite(parseInt(rangeParts[1] as string))) { + const from = formatAmountToLocalizedNumerals(parseInt(rangeParts[0] as string), defaultCurrency.value); + const to = formatAmountToLocalizedNumerals(parseInt(rangeParts[1] as string), defaultCurrency.value); + displayName = `${from} ~ ${to}`; + } + } + return displayName; }