From 438410708f5e2d07a3b85023858d2675e285e1b3 Mon Sep 17 00:00:00 2001 From: CaIon Date: Wed, 29 Apr 2026 10:23:15 +0800 Subject: [PATCH] feat(charts): enhance tooltip functionality and improve data sorting logic --- .../src/features/dashboard/lib/charts.ts | 104 ++++++++++++------ 1 file changed, 68 insertions(+), 36 deletions(-) diff --git a/web/default/src/features/dashboard/lib/charts.ts b/web/default/src/features/dashboard/lib/charts.ts index d1c1baab..96c9834c 100644 --- a/web/default/src/features/dashboard/lib/charts.ts +++ b/web/default/src/features/dashboard/lib/charts.ts @@ -9,6 +9,16 @@ import type { } from '@/features/dashboard/types' type TFunction = (key: string) => string +type TooltipLineItem = { + key: string + value: string | number + datum?: Record + hasShape?: boolean + shapeType?: string + shapeFill?: string + shapeStroke?: string + shapeSize?: number +} function getVChartDefaultColors(domainLength: number) { const scheme = @@ -19,15 +29,6 @@ function getVChartDefaultColors(domainLength: number) { return scheme.scheme } -function buildModelColorSpec(models: string[]) { - const domain = Array.from(new Set(models)) - return { - type: 'ordinal', - domain, - range: getVChartDefaultColors(domain.length), - } -} - function renderQuotaCompat(rawQuota: number, digits = 4): string { const { config, meta } = getCurrencyDisplay() if (meta.kind === 'tokens') return rawQuota.toLocaleString() @@ -59,19 +60,24 @@ export function processChartData( const formatQuotaTotal = (value: number) => renderQuotaCompat(value, 2) const MAX_TOOLTIP_MODELS = 15 + const isOtherTooltipKey = (key: string) => + key === 'Other' || key === otherLabel + + const makeTooltipDimensionUpdateContent = (options?: { + collapseOverflow?: boolean + }) => { + const collapseOverflow = options?.collapseOverflow ?? true + + return (array: TooltipLineItem[]) => { + const modelItems = array.filter((item) => !isOtherTooltipKey(item.key)) + const otherItems = array.filter((item) => isOtherTooltipKey(item.key)) + modelItems.sort( + (a, b) => (Number(b.value) || 0) - (Number(a.value) || 0) + ) + array = [...modelItems, ...otherItems] - const makeTooltipDimensionUpdateContent = () => { - return ( - array: Array<{ - key: string - value: string | number - datum?: Record - }> - ) => { - array.sort((a, b) => (Number(b.value) || 0) - (Number(a.value) || 0)) let sum = 0 for (let i = 0; i < array.length; i++) { - if (array[i].key === 'Other' || array[i].key === otherLabel) continue const v = Number(array[i].value) || 0 if ( array[i].datum && @@ -83,20 +89,27 @@ export function processChartData( array[i].value = formatQuotaValue(v) } - if (array.length > MAX_TOOLTIP_MODELS) { - const visible = array.slice(0, MAX_TOOLTIP_MODELS) - let otherSum = 0 - for (let i = MAX_TOOLTIP_MODELS; i < array.length; i++) { - const raw = array[i].datum - ? Number((array[i].datum as Record)?.rawQuota) || 0 - : 0 - otherSum += raw - } - visible.push({ - key: otherLabel, - value: formatQuotaValue(otherSum), - }) - array = visible + if (collapseOverflow && array.length > MAX_TOOLTIP_MODELS) { + const visible = modelItems.slice(0, MAX_TOOLTIP_MODELS) + const otherSum = [...modelItems.slice(MAX_TOOLTIP_MODELS), ...otherItems] + .reduce((sum, item) => { + const raw = item.datum + ? Number((item.datum as Record)?.rawQuota) || 0 + : 0 + return sum + raw + }, 0) + array = [ + ...visible, + { + key: otherLabel, + value: formatQuotaValue(otherSum), + hasShape: true, + shapeType: 'square', + shapeFill: otherTooltipColor, + shapeStroke: otherTooltipColor, + shapeSize: 8, + }, + ] } array.unshift({ @@ -226,7 +239,16 @@ export function processChartData( const allModels = Array.from(modelTotalsMap.keys()) const sortedTimes = Array.from(timeModelMap.keys()).sort() const sortedModels = [...allModels].sort() - const modelColor = buildModelColorSpec([...sortedModels, otherLabel]) + const modelColorDomain = Array.from(new Set([...sortedModels, otherLabel])) + const modelColorRange = getVChartDefaultColors(modelColorDomain.length) + const otherColor = modelColorRange[modelColorDomain.indexOf(otherLabel)] + const otherTooltipColor = + typeof otherColor === 'string' ? otherColor : '#FF8A00' + const modelColor = { + type: 'ordinal', + domain: modelColorDomain, + range: modelColorRange, + } // Pad time points if too few (default 7 points) const MAX_TREND_POINTS = MAX_CHART_TREND_POINTS @@ -508,7 +530,9 @@ export function processChartData( Number(datum?.rawQuota) || 0, }, ], - updateContent: makeTooltipDimensionUpdateContent(), + updateContent: makeTooltipDimensionUpdateContent({ + collapseOverflow: false, + }), }, }, area: { @@ -564,9 +588,17 @@ export function processChartData( value: string | number }> ) => { - array.sort( + const modelItems = array.filter( + (item) => !isOtherTooltipKey(item.key) + ) + const otherItems = array.filter((item) => + isOtherTooltipKey(item.key) + ) + modelItems.sort( (a, b) => (Number(b.value) || 0) - (Number(a.value) || 0) ) + array = [...modelItems, ...otherItems] + let sum = 0 for (let i = 0; i < array.length; i++) { const v = Number(array[i].value) || 0