diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts index 40834e646f71c..841d8f44225f6 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts @@ -62,6 +62,7 @@ import { extractDataTotalValues, extractSeries, extractShowValueIndexes, + extractTooltipKeys, getAxisType, getColtypesMapping, getLegendProps, @@ -581,9 +582,13 @@ export default function transformProps( : params.value[0]; const forecastValue: any[] = richTooltip ? params : [params]; - if (richTooltip && tooltipSortByMetric) { - forecastValue.sort((a, b) => b.data[1] - a.data[1]); - } + const sortedKeys = extractTooltipKeys( + forecastValue, + // horizontal mode is not supported in mixed series chart + 1, + richTooltip, + tooltipSortByMetric, + ); const rows: string[][] = []; const forecastValues = @@ -591,44 +596,46 @@ export default function transformProps( const keys = Object.keys(forecastValues); let focusedRow; - keys.forEach(key => { - const value = forecastValues[key]; - // if there are no dimensions, key is a verbose name of a metric, - // otherwise it is a comma separated string where the first part is metric name - let formatterKey; - if (primarySeries.has(key)) { - formatterKey = - groupby.length === 0 ? inverted[key] : labelMap[key]?.[0]; - } else { - formatterKey = - groupbyB.length === 0 ? inverted[key] : labelMapB[key]?.[0]; - } - const tooltipFormatter = getFormatter( - customFormatters, - formatter, - metrics, - formatterKey, - !!contributionMode, - ); - const tooltipFormatterSecondary = getFormatter( - customFormattersSecondary, - formatterSecondary, - metricsB, - formatterKey, - !!contributionMode, - ); - const row = formatForecastTooltipSeries({ - ...value, - seriesName: key, - formatter: primarySeries.has(key) - ? tooltipFormatter - : tooltipFormatterSecondary, + sortedKeys + .filter(key => keys.includes(key)) + .forEach(key => { + const value = forecastValues[key]; + // if there are no dimensions, key is a verbose name of a metric, + // otherwise it is a comma separated string where the first part is metric name + let formatterKey; + if (primarySeries.has(key)) { + formatterKey = + groupby.length === 0 ? inverted[key] : labelMap[key]?.[0]; + } else { + formatterKey = + groupbyB.length === 0 ? inverted[key] : labelMapB[key]?.[0]; + } + const tooltipFormatter = getFormatter( + customFormatters, + formatter, + metrics, + formatterKey, + !!contributionMode, + ); + const tooltipFormatterSecondary = getFormatter( + customFormattersSecondary, + formatterSecondary, + metricsB, + formatterKey, + !!contributionMode, + ); + const row = formatForecastTooltipSeries({ + ...value, + seriesName: key, + formatter: primarySeries.has(key) + ? tooltipFormatter + : tooltipFormatterSecondary, + }); + rows.push(row); + if (key === focusedSeries) { + focusedRow = rows.length - 1; + } }); - rows.push(row); - if (key === focusedSeries) { - focusedRow = rows.length - 1; - } - }); return tooltipHtml(rows, tooltipFormatter(xValue), focusedRow); }, }, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts index 8794ee660b9fe..ca1441cd28bc0 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts @@ -65,6 +65,7 @@ import { extractDataTotalValues, extractSeries, extractShowValueIndexes, + extractTooltipKeys, getAxisType, getColtypesMapping, getLegendProps, @@ -249,7 +250,7 @@ export default function transformProps( legendState, }); const seriesContexts = extractForecastSeriesContexts( - Object.values(rawSeries).map(series => series.name as string), + rawSeries.map(series => series.name as string), ); const isAreaExpand = stack === StackControlsValue.Expand; const xAxisDataType = dataTypes?.[xAxisLabel] ?? dataTypes?.[xAxisOrig]; @@ -539,11 +540,12 @@ export default function transformProps( ? params[0].value[xIndex] : params.value[xIndex]; const forecastValue: any[] = richTooltip ? params : [params]; - - if (richTooltip && tooltipSortByMetric) { - forecastValue.sort((a, b) => b.data[yIndex] - a.data[yIndex]); - } - + const sortedKeys = extractTooltipKeys( + forecastValue, + yIndex, + richTooltip, + tooltipSortByMetric, + ); const forecastValues: Record = extractForecastValuesFromTooltipParams(forecastValue, isHorizontal); @@ -566,24 +568,28 @@ export default function transformProps( const showPercentage = showTotal && !forcePercentFormatter; const keys = Object.keys(forecastValues); let focusedRow; - keys.forEach(key => { - const value = forecastValues[key]; - if (value.observation === 0 && stack) { - return; - } - const row = formatForecastTooltipSeries({ - ...value, - seriesName: key, - formatter, + sortedKeys + .filter(key => keys.includes(key)) + .forEach(key => { + const value = forecastValues[key]; + if (value.observation === 0 && stack) { + return; + } + const row = formatForecastTooltipSeries({ + ...value, + seriesName: key, + formatter, + }); + if (showPercentage && value.observation !== undefined) { + row.push( + percentFormatter.format(value.observation / (total || 1)), + ); + } + rows.push(row); + if (key === focusedSeries) { + focusedRow = rows.length - 1; + } }); - if (showPercentage && value.observation !== undefined) { - row.push(percentFormatter.format(value.observation / (total || 1))); - } - rows.push(row); - if (key === focusedSeries) { - focusedRow = rows.length - 1; - } - }); if (stack) { rows.reverse(); if (focusedRow !== undefined) { diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts b/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts index 13d36b71418ba..0aa0ae988ee98 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts @@ -642,3 +642,22 @@ export function getTimeCompareStackId( }) || defaultId ); } + +const TOOLTIP_SERIES_KEY = 'seriesId'; +export function extractTooltipKeys( + forecastValue: any[], + yIndex: number, + richTooltip?: boolean, + tooltipSortByMetric?: boolean, +): string[] { + if (richTooltip && tooltipSortByMetric) { + return forecastValue + .slice() + .sort((a, b) => b.data[yIndex] - a.data[yIndex]) + .map(value => value[TOOLTIP_SERIES_KEY]); + } + if (richTooltip) { + return forecastValue.map(s => s[TOOLTIP_SERIES_KEY]); + } + return [forecastValue[0][TOOLTIP_SERIES_KEY]]; +} diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/utils/series.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/utils/series.test.ts index efc0ac745aedf..7054f6019ad30 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/test/utils/series.test.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/test/utils/series.test.ts @@ -31,6 +31,7 @@ import { extractGroupbyLabel, extractSeries, extractShowValueIndexes, + extractTooltipKeys, formatSeriesName, getAxisType, getChartPadding, @@ -1072,3 +1073,29 @@ describe('getTimeCompareStackId', () => { expect(result).toEqual('123'); }); }); + +const forecastValue = [ + { + data: [0, 1], + seriesId: 'foo', + }, + { + data: [0, 2], + seriesId: 'bar', + }, +]; + +test('extractTooltipKeys with rich tooltip', () => { + const result = extractTooltipKeys(forecastValue, 1, true, false); + expect(result).toEqual(['foo', 'bar']); +}); + +test('extractTooltipKeys with rich tooltip and sorting by metrics', () => { + const result = extractTooltipKeys(forecastValue, 1, true, true); + expect(result).toEqual(['bar', 'foo']); +}); + +test('extractTooltipKeys with non-rich tooltip', () => { + const result = extractTooltipKeys(forecastValue, 1, false, false); + expect(result).toEqual(['foo']); +});