From 2cf1486f768145a2e29aa5b20ec69be6404fe42b Mon Sep 17 00:00:00 2001 From: Anton Kulyk <kuliks.anton@gmail.com> Date: Fri, 18 Oct 2024 16:26:44 +0100 Subject: [PATCH] Optimize pie charts rendering with many rows (#48670) --- .../view/sidebars/ChartSettingsSidebar.jsx | 4 +- .../src/metabase/query_builder/selectors.js | 4 +- .../src/metabase/query_builder/utils/index.ts | 2 +- .../settings/ChartSettingSeriesOrder.tsx | 2 + .../visualizations/echarts/pie/model/index.ts | 24 ++++++++-- .../visualizations/echarts/pie/util/colors.ts | 44 +++---------------- .../CartesianChart/chart-definition-legacy.js | 2 + .../visualizations/PieChart/PieRowsPicker.tsx | 30 +++++++++++-- .../PieChart/chart-definition.ts | 40 ++++++++++++----- 9 files changed, 90 insertions(+), 62 deletions(-) diff --git a/frontend/src/metabase/query_builder/components/view/sidebars/ChartSettingsSidebar.jsx b/frontend/src/metabase/query_builder/components/view/sidebars/ChartSettingsSidebar.jsx index bbbee7cc7ba..6b622974017 100644 --- a/frontend/src/metabase/query_builder/components/view/sidebars/ChartSettingsSidebar.jsx +++ b/frontend/src/metabase/query_builder/components/view/sidebars/ChartSettingsSidebar.jsx @@ -43,11 +43,11 @@ function ChartSettingsSidebar(props) { const series = useMemo(() => { return [ { + ...result, card, - data: result.data, }, ]; - }, [card, result.data]); + }, [card, result]); return ( result && ( diff --git a/frontend/src/metabase/query_builder/selectors.js b/frontend/src/metabase/query_builder/selectors.js index 34f67c055e2..d23ef313fab 100644 --- a/frontend/src/metabase/query_builder/selectors.js +++ b/frontend/src/metabase/query_builder/selectors.js @@ -697,9 +697,10 @@ export const getRawSeries = createSelector( datasetQuery: lastRunDatasetQuery, }); if (isShowingRawTable && rawSeries?.length > 0) { - const [{ card, data }] = rawSeries; + const [{ card, ...rest }] = rawSeries; return [ { + ...rest, card: { ...card, display: "table", @@ -708,7 +709,6 @@ export const getRawSeries = createSelector( "table.pivot": false, }, }, - data, }, ]; } diff --git a/frontend/src/metabase/query_builder/utils/index.ts b/frontend/src/metabase/query_builder/utils/index.ts index 52d4904b9b0..4d1c282213a 100644 --- a/frontend/src/metabase/query_builder/utils/index.ts +++ b/frontend/src/metabase/query_builder/utils/index.ts @@ -158,11 +158,11 @@ export const createRawSeries = (options: { return ( queryResult && [ { + ...queryResult, card: { ...question.card(), ...(datasetQuery && { dataset_query: datasetQuery }), }, - data: queryResult && queryResult.data, }, ] ); diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingSeriesOrder.tsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingSeriesOrder.tsx index 863d2f5e302..2ce266ac9e3 100644 --- a/frontend/src/metabase/visualizations/components/settings/ChartSettingSeriesOrder.tsx +++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingSeriesOrder.tsx @@ -20,6 +20,8 @@ import { ChartSettingOrderedSimpleRoot, } from "./ChartSettingOrderedSimple.styled"; +export { SortableChartSettingOrderedItem }; + export interface SortableItem { key: string; enabled: boolean; diff --git a/frontend/src/metabase/visualizations/echarts/pie/model/index.ts b/frontend/src/metabase/visualizations/echarts/pie/model/index.ts index 1de858ad9c3..1ac3eeec665 100644 --- a/frontend/src/metabase/visualizations/echarts/pie/model/index.ts +++ b/frontend/src/metabase/visualizations/echarts/pie/model/index.ts @@ -28,7 +28,7 @@ import { } from "../constants"; import { getDimensionFormatter } from "../format"; import { getArrayFromMapValues } from "../util"; -import { getColorForRing } from "../util/colors"; +import { createHexToAccentNumberMap, getRingColorAlias } from "../util/colors"; import type { PieChartModel, @@ -317,6 +317,23 @@ export function getPieChartModel( return currTotal + value; }, 0); + const hexToAccentColorMap = createHexToAccentNumberMap(); + + function getColorForRing( + hexColor: string, + ring: "inner" | "middle" | "outer", + hasMultipleRings: boolean, + ) { + if (!hasMultipleRings) { + return hexColor; + } + const accentNumber = hexToAccentColorMap.get(hexColor); + if (accentNumber == null) { + return hexColor; + } + return renderingContext.getColor(getRingColorAlias(accentNumber, ring)); + } + // Create sliceTree, fill out the innermost slice ring const sliceTree: SliceTree = new Map(); const [sliceTreeNodes, others] = _.chain(pieRowsWithValues) @@ -335,7 +352,6 @@ export function getPieChartModel( color, "inner", colDescs.middleDimensionDesc != null, - renderingContext, ), visible, children: new Map(), @@ -403,7 +419,7 @@ export function getPieChartModel( colDescs.middleDimensionDesc, formatMiddleDimensionValue, dimensionNode, - getColorForRing(dimensionNode.color, "middle", true, renderingContext), + getColorForRing(dimensionNode.color, "middle", true), index, total, colDescs.outerDimensionDesc == null ? showWarning : undefined, @@ -423,7 +439,7 @@ export function getPieChartModel( colDescs.outerDimensionDesc, formatOuterDimensionValue, middleDimensionNode, - getColorForRing(dimensionNode.color, "outer", true, renderingContext), + getColorForRing(dimensionNode.color, "outer", true), index, total, showWarning, diff --git a/frontend/src/metabase/visualizations/echarts/pie/util/colors.ts b/frontend/src/metabase/visualizations/echarts/pie/util/colors.ts index 314572bafb2..ebd017d5c7b 100644 --- a/frontend/src/metabase/visualizations/echarts/pie/util/colors.ts +++ b/frontend/src/metabase/visualizations/echarts/pie/util/colors.ts @@ -1,13 +1,9 @@ import { aliases, colors } from "metabase/lib/colors"; import { checkNumber } from "metabase/lib/types"; -import type { - ColorGetter, - RenderingContext, -} from "metabase/visualizations/types"; const ACCENT_KEY_PREFIX = "accent"; -function getAccentNumberFromHex(hexColor: string) { +export function createHexToAccentNumberMap() { const hexToAccentNumber = new Map<string, number>(); for (const [key, hex] of Object.entries(colors)) { @@ -35,24 +31,13 @@ function getAccentNumberFromHex(hexColor: string) { hexToAccentNumber.set(hex, accentNumber); } - return hexToAccentNumber.get(hexColor); + return hexToAccentNumber; } -export function getColorForRing( - hexColor: string, +export function getRingColorAlias( + accentColorNumber: number, ring: "inner" | "middle" | "outer", - hasMultipleRings: boolean, - renderingContext: RenderingContext, ) { - if (!hasMultipleRings) { - return hexColor; - } - - const accentNumber = getAccentNumberFromHex(hexColor); - if (accentNumber == null) { - return hexColor; - } - let suffix = ""; if (ring === "inner") { suffix = "-dark"; @@ -60,24 +45,9 @@ export function getColorForRing( suffix = "-light"; } - return renderingContext.getColor( - `${ACCENT_KEY_PREFIX}${accentNumber}${suffix}`, - ); + return `${ACCENT_KEY_PREFIX}${accentColorNumber}${suffix}`; } -export function getColorForPicker( - hexColor: string | undefined, - hasMultipleRings: boolean, - getColor: ColorGetter, -) { - if (!hasMultipleRings || hexColor == null) { - return hexColor; - } - - const accentNumber = getAccentNumberFromHex(hexColor); - if (accentNumber == null) { - return hexColor; - } - - return getColor(`${ACCENT_KEY_PREFIX}${accentNumber}-dark`); +export function getPickerColorAlias(accentNumber: number) { + return `${ACCENT_KEY_PREFIX}${accentNumber}-dark`; } diff --git a/frontend/src/metabase/visualizations/visualizations/CartesianChart/chart-definition-legacy.js b/frontend/src/metabase/visualizations/visualizations/CartesianChart/chart-definition-legacy.js index e09fb0c90bc..b20908ab6fb 100644 --- a/frontend/src/metabase/visualizations/visualizations/CartesianChart/chart-definition-legacy.js +++ b/frontend/src/metabase/visualizations/visualizations/CartesianChart/chart-definition-legacy.js @@ -77,6 +77,7 @@ function transformSingleSeries(s, series, seriesIndex) { } return breakoutValues.map(breakoutValue => ({ + ...s, card: { ...card, // if multiseries include the card title as well as the breakout value @@ -133,6 +134,7 @@ function transformSingleSeries(s, series, seriesIndex) { .join(": "); return { + ...s, card: { ...card, name: name, diff --git a/frontend/src/metabase/visualizations/visualizations/PieChart/PieRowsPicker.tsx b/frontend/src/metabase/visualizations/visualizations/PieChart/PieRowsPicker.tsx index 1e939f0070f..70384a345d9 100644 --- a/frontend/src/metabase/visualizations/visualizations/PieChart/PieRowsPicker.tsx +++ b/frontend/src/metabase/visualizations/visualizations/PieChart/PieRowsPicker.tsx @@ -1,10 +1,15 @@ +import { useMemo } from "react"; + import { color } from "metabase/lib/colors"; import { ChartSettingSeriesOrder, - type SortableItem, + type SortableChartSettingOrderedItem, } from "metabase/visualizations/components/settings/ChartSettingSeriesOrder"; import type { PieRow } from "metabase/visualizations/echarts/pie/model/types"; -import { getColorForPicker } from "metabase/visualizations/echarts/pie/util/colors"; +import { + createHexToAccentNumberMap, + getPickerColorAlias, +} from "metabase/visualizations/echarts/pie/util/colors"; import type { ComputedVisualizationSettings } from "metabase/visualizations/types"; import type { RawSeries } from "metabase-types/api"; @@ -22,10 +27,27 @@ export function PieRowsPicker({ onShowWidget: (widget: any, ref: any) => void; }) { const pieRows = settings["pie.rows"]; + const hasMultipleRings = numRings > 1; + + const hexToAccentNumberMap = useMemo(() => createHexToAccentNumberMap(), []); + if (pieRows == null) { return null; } + const handleGetColorForPicker = ({ + color: hexColor, + }: SortableChartSettingOrderedItem) => { + if (!hasMultipleRings || hexColor == null) { + return hexColor; + } + const accentNumber = hexToAccentNumberMap.get(hexColor); + if (accentNumber == null) { + return hexColor; + } + return color(getPickerColorAlias(accentNumber)); + }; + const onChangeSeriesColor = (sliceKey: string, color: string) => onChangeSettings({ "pie.rows": pieRows.map(row => { @@ -36,7 +58,7 @@ export function PieRowsPicker({ }), }); - const onSortEnd = (newPieRows: SortableItem[]) => + const onSortEnd = (newPieRows: SortableChartSettingOrderedItem[]) => onChangeSettings({ "pie.sort_rows": false, "pie.rows": newPieRows as PieRow[], @@ -56,7 +78,7 @@ export function PieRowsPicker({ ? { dark: true, main: false, light: false, harmony: false } : undefined } - getItemColor={item => getColorForPicker(item.color, numRings > 1, color)} + getItemColor={handleGetColorForPicker} /> ); } diff --git a/frontend/src/metabase/visualizations/visualizations/PieChart/chart-definition.ts b/frontend/src/metabase/visualizations/visualizations/PieChart/chart-definition.ts index f25f3aa5393..ec4087f9f25 100644 --- a/frontend/src/metabase/visualizations/visualizations/PieChart/chart-definition.ts +++ b/frontend/src/metabase/visualizations/visualizations/PieChart/chart-definition.ts @@ -39,6 +39,14 @@ import type { RawSeries, Series } from "metabase-types/api"; import { DimensionsWidget } from "./DimensionsWidget"; import { SliceNameWidget } from "./SliceNameWidget"; +const pieRowsReadDeps = [ + "pie.dimension", + "pie.metric", + "pie.colors", + "pie.sort_rows", + "pie.slice_threshold", +]; + export const PIE_CHART_DEFINITION: VisualizationDefinition = { uiName: t`Pie`, identifier: "pie", @@ -113,18 +121,26 @@ export const PIE_CHART_DEFINITION: VisualizationDefinition = { }), "pie.rows": { hidden: true, - getValue: (rawSeries, settings) => { - return getPieRows(rawSeries, settings, (value, options) => - String(formatValue(value, options)), - ); - }, - readDependencies: [ - "pie.dimension", - "pie.metric", - "pie.colors", - "pie.sort_rows", - "pie.slice_threshold", - ], + getValue: _.memoize( + (series, settings) => { + return getPieRows(series, settings, (value, options) => + String(formatValue(value, options)), + ); + }, + ([{ json_query, started_at }], settings) => + JSON.stringify({ + json_query, + started_at, + settings: _.pick( + settings, + ...pieRowsReadDeps, + "column", + "pie.rows", + "pie.sort_rows_dimension", + ), + }), + ), + readDependencies: pieRowsReadDeps, writeDependencies: ["pie.sort_rows_dimension"], }, "pie.sort_rows_dimension": { -- GitLab