diff --git a/src/components/desktop/AxisChart.vue b/src/components/desktop/AxisChart.vue index 6d37037b..5ca5980d 100644 --- a/src/components/desktop/AxisChart.vue +++ b/src/components/desktop/AxisChart.vue @@ -1,5 +1,5 @@ @@ -24,7 +24,7 @@ import { isArray } from '@/lib/common.ts'; import { getDisplayColor } from '@/lib/color.ts'; import { sortStatisticsItems } from '@/lib/statistics.ts'; -export type AxisChartDisplayType = 'area' | 'column' | 'bubble'; +export type AxisChartDisplayType = 'line' | 'area' | 'column' | 'bubble'; interface AxisChartDataItem { id: string; @@ -49,9 +49,11 @@ interface AxisChartTooltipItem extends SortableTransactionStatisticDataItem { } const props = defineProps<{ + class?: string; skeleton?: boolean; type: AxisChartDisplayType; stacked?: boolean; + oneHundredPercentStacked?: boolean; sortingType: number; showValue?: boolean; showTotalAmountInTooltip?: boolean; @@ -72,7 +74,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'click', itemId: string, categoryIndex: number): void; + (e: 'click', itemId: string, categoryIndex: number, item: Record): void; }>(); const theme = useTheme(); @@ -81,13 +83,29 @@ const { tt, getCurrentLanguageTextDirection, formatAmountToWesternArabicNumeralsWithoutDigitGrouping, - formatAmountToLocalizedNumeralsWithCurrency + formatAmountToLocalizedNumeralsWithCurrency, + formatPercentToLocalizedNumerals } = useI18n(); const selectedLegends = ref>({}); const textDirection = computed(() => getCurrentLanguageTextDirection()); const isDarkMode = computed(() => theme.global.name.value === ThemeType.Dark); +const finalClass = computed(() => { + let finalClass = ''; + + if (props.skeleton) { + finalClass += 'transition-in'; + } + + if (props.class) { + finalClass += ` ${props.class}`; + } else { + finalClass += ' axis-chart-container'; + } + + return finalClass; +}); const itemsMap = computed>>(() => { const map: Record> = {}; @@ -125,7 +143,8 @@ const itemsMap = computed>>(() => { const allSeries = computed(() => { const allSeries: AxisChartDataItem[] = []; - let maxAmount: number = 0; + const categoryTotalAmount: Record = {}; + let maxAmountOfAllData: number = 0; for (const item of props.items) { if (props.hiddenField && item[props.hiddenField]) { @@ -138,11 +157,32 @@ const allSeries = computed(() => { const allAmounts: number[] = item[props.valuesField] as number[]; - if (props.type === 'bubble') { - for (const amount of allAmounts) { - if (amount > maxAmount) { - maxAmount = amount; - } + for (const [amount, categoryIndex] of itemAndIndex(allAmounts)) { + let totalAmount: number = categoryTotalAmount[categoryIndex] ?? 0; + totalAmount += amount; + categoryTotalAmount[categoryIndex] = totalAmount; + + if (amount > maxAmountOfAllData) { + maxAmountOfAllData = amount; + } + } + } + + for (const item of props.items) { + if (props.hiddenField && item[props.hiddenField]) { + continue; + } + + if (!isArray(item[props.valuesField])) { + continue; + } + + const allAmounts: number[] = item[props.valuesField] as number[]; + + if (props.oneHundredPercentStacked) { + for (const [amount, categoryIndex] of itemAndIndex(allAmounts)) { + const totalAmount: number = categoryTotalAmount[categoryIndex] ?? 0; + allAmounts[categoryIndex] = totalAmount !== 0 ? amount * 100.0 / totalAmount : 0; } } @@ -164,14 +204,16 @@ const allSeries = computed(() => { finalItem.stack = item[props.idField] as string; } - if (props.type === 'area') { + if (props.type === 'line') { + finalItem.areaStyle = undefined; + } else if (props.type === 'area') { finalItem.areaStyle = {}; } else if (props.type === 'column') { finalItem.type = 'bar'; } else if (props.type === 'bubble') { finalItem.type = 'scatter'; finalItem.symbolSize = (data: number): number => { - return Math.sqrt(data) / Math.sqrt(maxAmount) * 80 + 5; + return Math.sqrt(data) / Math.sqrt(maxAmountOfAllData) * 80 + 5; } } @@ -202,8 +244,8 @@ const yAxisWidth = computed(() => { } } - const maxValueText = props.amountValue ? formatAmountToLocalizedNumeralsWithCurrency(maxValue, props.defaultCurrency) : maxValue.toString(); - const minValueText = props.amountValue ? formatAmountToLocalizedNumeralsWithCurrency(minValue, props.defaultCurrency) : minValue.toString(); + const maxValueText = getDisplayValue(maxValue); + const minValueText = getDisplayValue(minValue); const maxLengthText = maxValueText.length > minValueText.length ? maxValueText : minValueText; const canvas = document.createElement('canvas'); @@ -268,7 +310,7 @@ const chartOptions = computed(() => { for (const item of displayItems) { if (displayItems.length === 1 || item.totalAmount !== 0) { - const value = props.amountValue ? formatAmountToLocalizedNumeralsWithCurrency(item.totalAmount, props.defaultCurrency) : item.totalAmount.toString(); + const value = getDisplayValue(item.totalAmount); tooltip += '
'; tooltip += `${item.name}${value}
`; tooltip += '
'; @@ -276,8 +318,8 @@ const chartOptions = computed(() => { } } - if (props.showTotalAmountInTooltip) { - const displayTotalAmount = props.amountValue ? formatAmountToLocalizedNumeralsWithCurrency(totalAmount, props.defaultCurrency) : totalAmount.toString(); + if (props.showTotalAmountInTooltip && !props.oneHundredPercentStacked) { + const displayTotalAmount = getDisplayValue(totalAmount); tooltip = (actualDisplayItemCount > 0 ? '
' : '
') + '' + `${props.totalNameInTooltip}${displayTotalAmount}
` @@ -322,16 +364,18 @@ const chartOptions = computed(() => { yAxis: [ { type: 'value', + min: props.oneHundredPercentStacked ? 0 : undefined, + max: props.oneHundredPercentStacked ? 100 : undefined, axisLabel: { color: isDarkMode.value ? '#888' : '#666', formatter: (value: string) => { - return props.amountValue ? formatAmountToLocalizedNumeralsWithCurrency(parseInt(value), props.defaultCurrency): value; + return getDisplayValue(parseInt(value)); } }, axisPointer: { label: { formatter: (params: CallbackDataParams) => { - return props.amountValue ? formatAmountToLocalizedNumeralsWithCurrency(Math.trunc(params.value as number), props.defaultCurrency) : params.value; + return getDisplayValue(Math.trunc(params.value as number)); } } }, @@ -350,6 +394,18 @@ function getItemName(name: string): string { return props.translateName ? tt(name) : name; } +function getDisplayValue(value: number): string { + if (props.oneHundredPercentStacked) { + return formatPercentToLocalizedNumerals(value, 2, '<0.01'); + } + + if (props.amountValue) { + return formatAmountToLocalizedNumeralsWithCurrency(value, props.defaultCurrency); + } + + return value.toString(); +} + function clickItem(e: ECElementEvent): void { if (!props.enableClickItem || e.componentType !== 'series') { return; @@ -364,7 +420,7 @@ function clickItem(e: ECElementEvent): void { return; } - emit('click', itemId, e.dataIndex); + emit('click', itemId, e.dataIndex, item); } function exportData(): { headers: string[], data: string[][] } { @@ -404,13 +460,13 @@ defineExpose({ diff --git a/src/components/desktop/TrendsChart.vue b/src/components/desktop/TrendsChart.vue index 5c7e079d..2d4bc105 100644 --- a/src/components/desktop/TrendsChart.vue +++ b/src/components/desktop/TrendsChart.vue @@ -1,5 +1,5 @@