From 3485822494dcbdc81436fd0c68cc1362db805409 Mon Sep 17 00:00:00 2001 From: Jose Quintas Date: Fri, 5 Dec 2025 12:55:49 +0100 Subject: [PATCH 1/9] impl --- .../useChartVisibilityManager/index.ts | 3 + .../isIdentifierVisible.ts | 23 ++++ .../useChartVisibilityManager.selectors.ts | 44 +++++++ .../useChartVisibilityManager.ts | 109 ++++++++++++++++ .../useChartVisibilityManager.types.ts | 118 ++++++++++++++++++ 5 files changed, 297 insertions(+) create mode 100644 packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/index.ts create mode 100644 packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/isIdentifierVisible.ts create mode 100644 packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.selectors.ts create mode 100644 packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.ts create mode 100644 packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.types.ts diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/index.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/index.ts new file mode 100644 index 0000000000000..18a31fe99d68c --- /dev/null +++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/index.ts @@ -0,0 +1,3 @@ +export { useChartVisibilityManager } from './useChartVisibilityManager'; +export * from './useChartVisibilityManager.types'; +export * from './useChartVisibilityManager.selectors'; diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/isIdentifierVisible.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/isIdentifierVisible.ts new file mode 100644 index 0000000000000..02d374fea6e18 --- /dev/null +++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/isIdentifierVisible.ts @@ -0,0 +1,23 @@ +import type { VisibilityMap } from './useChartVisibilityManager.types'; + +export const VISIBILITY_SEPARATOR = '-'; + +export const buildIdentifier = (ids: string | (string | number)[]) => { + if (typeof ids === 'string') { + return ids; + } + return ids.filter((v) => v !== undefined && v !== null).join(VISIBILITY_SEPARATOR); +}; + +export const isIdentifierVisible = ( + visibilityMap: VisibilityMap, + identifier: string | (string | number)[], +) => { + if (Array.isArray(identifier)) { + identifier = buildIdentifier(identifier); + } + + const state = visibilityMap?.[identifier]; + + return state !== false; +}; diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.selectors.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.selectors.ts new file mode 100644 index 0000000000000..4aa5953c02871 --- /dev/null +++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.selectors.ts @@ -0,0 +1,44 @@ +import { createSelector, createSelectorMemoized } from '@mui/x-internals/store'; +import type { UseChartVisibilityManagerSignature } from './useChartVisibilityManager.types'; +import { type ChartOptionalRootSelector } from '../../utils/selectors'; +import { isIdentifierVisible } from './isIdentifierVisible'; + +/** + * Selector to get the visibility manager state. + */ +const selectVisibilityManager: ChartOptionalRootSelector = ( + state, +) => state.visibilityManager; + +export const EMPTY_VISIBILITY_MAP = {}; + +/** + * Selector to get the hidden identifiers from the visibility manager. + */ +export const selectorVisibilityMap = createSelector( + selectVisibilityManager, + (visibilityManager) => visibilityManager?.visibilityMap ?? EMPTY_VISIBILITY_MAP, +); + +/** + * Selector to check if a specific item identifier is visible. + */ +export const selectorIsIdentifierVisible = createSelector( + selectorVisibilityMap, + (visibilityMap, identifier: string | (string | number)[]) => + isIdentifierVisible(visibilityMap, identifier), +); + +/** + * Selector to check if a specific item identifier is hidden. + */ +export const selectorIsIdentifierVisibleGetter = createSelectorMemoized( + selectorVisibilityMap, + (visibilityMap) => { + return { + // Return an object as selectors don't correctly memoize direct functions + get: (identifier: string | (string | number)[]) => + isIdentifierVisible(visibilityMap, identifier), + }; + }, +); diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.ts new file mode 100644 index 0000000000000..b062837784322 --- /dev/null +++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.ts @@ -0,0 +1,109 @@ +'use client'; +import useEventCallback from '@mui/utils/useEventCallback'; +import { useEffectAfterFirstRender } from '@mui/x-internals/useEffectAfterFirstRender'; +import { type ChartPlugin } from '../../models'; +import { type UseChartVisibilityManagerSignature } from './useChartVisibilityManager.types'; +import { buildIdentifier as buildIdentifierFn } from './isIdentifierVisible'; +import { EMPTY_VISIBILITY_MAP } from './useChartVisibilityManager.selectors'; + +export const useChartVisibilityManager: ChartPlugin = ({ + store, + params, +}) => { + // Manage controlled state + useEffectAfterFirstRender(() => { + if (params.visibilityMap === undefined) { + return; + } + + if (process.env.NODE_ENV !== 'production' && !store.state.visibilityManager.isControlled) { + console.error( + [ + `MUI X Charts: A chart component is changing the \`visibilityMap\` from uncontrolled to controlled.`, + 'Elements should not switch from uncontrolled to controlled (or vice versa).', + 'Decide between using a controlled or uncontrolled for the lifetime of the component.', + "The nature of the state is determined during the first render. It's considered controlled if the value is not `undefined`.", + 'More info: https://fb.me/react-controlled-components', + ].join('\n'), + ); + } + store.set('visibilityManager', { + ...store.state.visibilityManager, + visibilityMap: params.visibilityMap, + }); + }, [store, params.visibilityMap]); + + const buildIdentifier = useEventCallback((ids: string | (string | number)[]) => + buildIdentifierFn(ids), + ); + + const hideItem = useEventCallback((identifier: string | (number | string)[]) => { + const visibilityMap = store.state.visibilityManager.visibilityMap; + const id = buildIdentifier(identifier); + + if (visibilityMap[id] === false) { + return; // Already hidden + } + + const newVisibility = { ...visibilityMap, [id]: false }; + store.set('visibilityManager', { + ...store.state.visibilityManager, + visibilityMap: newVisibility, + }); + + params.onVisibilityChange?.(newVisibility); + }); + + const showItem = useEventCallback((identifier: string | (number | string)[]) => { + const visibilityMap = store.state.visibilityManager.visibilityMap; + const id = buildIdentifier(identifier); + + if (visibilityMap[id] !== false) { + return; // Already visible + } + + const newVisibility = { ...visibilityMap, [id]: true }; + store.set('visibilityManager', { + ...store.state.visibilityManager, + visibilityMap: newVisibility, + }); + + params.onVisibilityChange?.(newVisibility); + }); + + const toggleItem = useEventCallback((identifier: string | (number | string)[]) => { + const visibilityMap = store.state.visibilityManager.visibilityMap; + const id = buildIdentifier(identifier); + + if (visibilityMap[id] === false) { + showItem(id); + } else { + hideItem(id); + } + }); + + return { + instance: { + hideItem, + showItem, + toggleItem, + }, + publicAPI: { + hideItem, + showItem, + toggleItem, + }, + }; +}; + +useChartVisibilityManager.getInitialState = (params) => ({ + visibilityManager: { + visibilityMap: params.visibilityMap || EMPTY_VISIBILITY_MAP, + isControlled: params.visibilityMap !== undefined, + }, +}); + +useChartVisibilityManager.params = { + onVisibilityChange: true, + visibilityMap: true, +}; diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.types.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.types.ts new file mode 100644 index 0000000000000..553c18da8b776 --- /dev/null +++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.types.ts @@ -0,0 +1,118 @@ +import { type ChartPluginSignature } from '../../models'; +import { type UseChartSeriesSignature } from '../../corePlugins/useChartSeries'; + +export type VisibilityMap = { + [key: string]: boolean; +}; + +export type IsIdentifierVisibleFunction = { + /** + * Function to check if an item is visible based on its identifier. + * @param {string | (string | number)[]} identifier The identifier of the item. + * @returns {boolean} Whether the item is visible. + */ + (identifier: string): boolean; + /** + * Function overload to check if an item is visible based on its identifier array. + * @param {(string | number)[]} identifierArray The identifier array of the item. + * @returns {boolean} Whether the item is visible. + */ + (identifierArray: (string | number)[]): boolean; +}; + +export interface UseChartVisibilityManagerPublicAPI { + /** + * Hide an item by its identifier. + * + * @param {string} identifier The identifier of the item to hide. + */ + hideItem(identifier: string): void; + /** + * Hide an item by providing an array of identifiers to join. + * + * The array will be joined using '-'. + * + * @param {(number | string)[]} identifierArray The identifiers array of the item to hide. + */ + hideItem(identifierArray: (number | string)[]): void; + /** + * Show an item by its identifier. + * + * @param {string} identifier The identifier of the item to show. + */ + showItem(identifier: string): void; + /** + * Show an item by providing an array of identifiers to join. + * + * The array will be joined using '-'. + * + * @param {(number | string)[]} identifierArray The identifiers array of the item to show. + */ + showItem(identifierArray: (number | string)[]): void; + /** + * Toggle the visibility of an item by its identifier. + * + * @param {string | (number | string)[]} identifier The identifier of the item to toggle. + */ + toggleItem(identifier: string): void; + /** + * Toggle the visibility of an item by providing an array of identifiers to join. + * + * The array will be joined using '-'. + * + * @param { (number | string)[]} identifierArray the identifiers array of the item to toggle. + */ + toggleItem(identifierArray: (number | string)[]): void; +} + +export interface UseChartVisibilityManagerInstance extends UseChartVisibilityManagerPublicAPI {} + +export interface UseChartVisibilityManagerParameters { + /** + * Callback fired when the visible series change. + * @param {{ [key: string]: boolean }} visibilityMap The new visibility map. + */ + onVisibilityChange?: (visibilityMap: Record) => void; + /** + * Map of the visibility status of series and/or items. + * + * Different chart types use different strategies to generate these keys. + * + * Generally, the key format is: + * - For series-level visibility: `${seriesId}` + * - For item-level visibility: `${seriesId}-${itemId}` + * + * @example + * { + * "series1": false, // series-level hidden + * "series2-itemA": false // item-level hidden + * } + */ + visibilityMap?: { + [key: string]: boolean; + }; +} + +export type UseChartVisibilityManagerDefaultizedParameters = UseChartVisibilityManagerParameters; + +export interface UseChartVisibilityManagerState { + visibilityManager: { + /** + * Map of identifiers visibility status. + */ + visibilityMap: VisibilityMap; + /** + * Internal information to know if the user controls the state or not. + */ + isControlled: boolean; + }; +} + +export type UseChartVisibilityManagerSignature = ChartPluginSignature<{ + instance: UseChartVisibilityManagerInstance; + publicAPI: UseChartVisibilityManagerPublicAPI; + state: UseChartVisibilityManagerState; + params: UseChartVisibilityManagerParameters; + defaultizedParams: UseChartVisibilityManagerDefaultizedParameters; + dependencies: [UseChartSeriesSignature]; +}>; From 94740e7f32349a9139caf92b1c0ae271563122e7 Mon Sep 17 00:00:00 2001 From: Jose Quintas Date: Fri, 5 Dec 2025 12:56:44 +0100 Subject: [PATCH 2/9] types --- packages/x-charts/src/models/seriesType/bar.ts | 4 +++- packages/x-charts/src/models/seriesType/config.ts | 5 ++++- packages/x-charts/src/models/seriesType/line.ts | 4 +++- packages/x-charts/src/models/seriesType/pie.ts | 6 +++++- packages/x-charts/src/models/seriesType/radar.ts | 4 +++- packages/x-charts/src/models/seriesType/scatter.ts | 1 + 6 files changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/x-charts/src/models/seriesType/bar.ts b/packages/x-charts/src/models/seriesType/bar.ts index 5c8f0a2d0a348..da7db8322e3b8 100644 --- a/packages/x-charts/src/models/seriesType/bar.ts +++ b/packages/x-charts/src/models/seriesType/bar.ts @@ -79,4 +79,6 @@ export interface DefaultizedBarSeriesType extends DefaultizedProps< BarSeriesType, CommonDefaultizedProps | 'color' | 'layout' | 'minBarSize' - > {} + > { + hidden: boolean; +} diff --git a/packages/x-charts/src/models/seriesType/config.ts b/packages/x-charts/src/models/seriesType/config.ts index 4939073e2415b..f6143ad3f7c62 100644 --- a/packages/x-charts/src/models/seriesType/config.ts +++ b/packages/x-charts/src/models/seriesType/config.ts @@ -134,7 +134,10 @@ export type ChartSeries = ChartsSeriesConfig[T]['seri export type ChartSeriesDefaultized = ChartsSeriesConfig[T] extends { canBeStacked: true; } - ? ChartsSeriesConfig[T]['series'] & { stackedData: [number, number][] } + ? ChartsSeriesConfig[T]['series'] & { + visibleStackedData: [number, number][]; + stackedData: [number, number][]; + } : ChartsSeriesConfig[T]['series']; export type ChartSeriesLayout = ChartsSeriesConfig[T] extends any diff --git a/packages/x-charts/src/models/seriesType/line.ts b/packages/x-charts/src/models/seriesType/line.ts index 390ed75ace37b..1d700835e0750 100644 --- a/packages/x-charts/src/models/seriesType/line.ts +++ b/packages/x-charts/src/models/seriesType/line.ts @@ -117,4 +117,6 @@ export type LineItemIdentifier = { }; export interface DefaultizedLineSeriesType - extends DefaultizedProps {} + extends DefaultizedProps { + hidden: boolean; +} diff --git a/packages/x-charts/src/models/seriesType/pie.ts b/packages/x-charts/src/models/seriesType/pie.ts index 825bfd99577e7..9baca50b723c5 100644 --- a/packages/x-charts/src/models/seriesType/pie.ts +++ b/packages/x-charts/src/models/seriesType/pie.ts @@ -24,7 +24,11 @@ export type PieValueType = { }; export type DefaultizedPieValueType = PieValueType & - Omit, 'data'> & { color: string; formattedValue: string }; + Omit, 'data' | 'hidden'> & { + color: string; + formattedValue: string; + hidden: boolean; + }; export type ChartsPieSorting = 'none' | 'asc' | 'desc' | ((a: number, b: number) => number); diff --git a/packages/x-charts/src/models/seriesType/radar.ts b/packages/x-charts/src/models/seriesType/radar.ts index adcc8c87dd730..6f823871e5b16 100644 --- a/packages/x-charts/src/models/seriesType/radar.ts +++ b/packages/x-charts/src/models/seriesType/radar.ts @@ -29,4 +29,6 @@ export type RadarItemIdentifier = { }; export interface DefaultizedRadarSeriesType - extends DefaultizedProps {} + extends DefaultizedProps { + hidden: boolean; +} diff --git a/packages/x-charts/src/models/seriesType/scatter.ts b/packages/x-charts/src/models/seriesType/scatter.ts index 79b9308e43d82..f53d4e355a9d4 100644 --- a/packages/x-charts/src/models/seriesType/scatter.ts +++ b/packages/x-charts/src/models/seriesType/scatter.ts @@ -87,4 +87,5 @@ export type ScatterItemIdentifier = { export interface DefaultizedScatterSeriesType extends DefaultizedProps { preview: MakeRequired, 'markerSize'>; + hidden: boolean; } From 8ebdb1fe870279d73e7e3a242b265476c08e1056 Mon Sep 17 00:00:00 2001 From: Jose Quintas Date: Fri, 5 Dec 2025 12:57:15 +0100 Subject: [PATCH 3/9] impl --- .../x-charts/src/BarChart/extremums.test.ts | 32 +++--- .../seriesConfig/bar/seriesProcessor.ts | 96 +++++++++++++--- .../seriesConfig/bar/tooltipPosition.ts | 1 + packages/x-charts/src/BarChart/types.ts | 1 + .../x-charts/src/BarChart/useBarPlotData.ts | 3 + .../LineChart/seriesConfig/seriesProcessor.ts | 108 ++++++++++++++---- .../x-charts/src/LineChart/useAreaPlotData.ts | 9 +- .../x-charts/src/LineChart/useLinePlotData.ts | 20 +++- .../PieChart/seriesConfig/seriesProcessor.ts | 61 +++++++--- .../seriesConfig/seriesProcessor.ts | 4 +- .../seriesConfig/seriesProcessor.ts | 7 +- .../src/internals/getBarDimensions.ts | 35 ++++-- packages/x-charts/src/internals/index.ts | 1 + .../useChartSeries/processSeries.ts | 5 +- .../useChartSeries.selectors.ts | 13 ++- ...useChartKeyboardNavigation.helpers.test.ts | 8 ++ .../seriesConfig/seriesProcessor.types.ts | 2 + packages/x-charts/src/plugins/index.ts | 6 + 18 files changed, 321 insertions(+), 91 deletions(-) diff --git a/packages/x-charts/src/BarChart/extremums.test.ts b/packages/x-charts/src/BarChart/extremums.test.ts index e2db1ed74a3c5..acfb216900a9d 100644 --- a/packages/x-charts/src/BarChart/extremums.test.ts +++ b/packages/x-charts/src/BarChart/extremums.test.ts @@ -5,6 +5,13 @@ const buildData = ( data: number[], layout: 'vertical' | 'horizontal' = 'vertical', ): Parameters>[0] => { + const stackData: [number, number][] = data.length + ? [ + [data[0], data[1]], + [data[2], data[3]], + ] + : []; + return { series: { id1: { @@ -13,13 +20,10 @@ const buildData = ( color: 'red', data, minBarSize: 0, - stackedData: data.length - ? [ - [data[0], data[1]], - [data[2], data[3]], - ] - : [], + stackedData: stackData, + visibleStackedData: stackData, layout, + hidden: false, valueFormatter: () => '', }, }, @@ -41,7 +45,12 @@ const buildDataWithAxisId = ( layout === 'horizontal' ? { yAxisId: 'axis-id', xAxisId: 'other-id' } : { xAxisId: 'axis-id', yAxisId: 'other-id' }; - + const stackData: [number, number][] = data.length + ? [ + [data[0], data[1]], + [data[2], data[3]], + ] + : []; return { series: { id1: { @@ -50,13 +59,10 @@ const buildDataWithAxisId = ( color: 'red', data, minBarSize: 0, - stackedData: data.length - ? [ - [data[0], data[1]], - [data[2], data[3]], - ] - : [], + stackedData: stackData, + visibleStackedData: stackData, layout, + hidden: false, valueFormatter: () => '', ...axesIds, }, diff --git a/packages/x-charts/src/BarChart/seriesConfig/bar/seriesProcessor.ts b/packages/x-charts/src/BarChart/seriesConfig/bar/seriesProcessor.ts index a11d6a65114b6..158f372ceec26 100644 --- a/packages/x-charts/src/BarChart/seriesConfig/bar/seriesProcessor.ts +++ b/packages/x-charts/src/BarChart/seriesConfig/bar/seriesProcessor.ts @@ -11,7 +11,7 @@ type BarDataset = DatasetType; const barValueFormatter = ((v) => v == null ? '' : v.toLocaleString()) as DefaultizedBarSeriesType['valueFormatter']; -const seriesProcessor: SeriesProcessor<'bar'> = (params, dataset) => { +const seriesProcessor: SeriesProcessor<'bar'> = (params, dataset, isIdentifierVisible) => { const { seriesOrder, series } = params; const stackingGroups = getStackingGroups(params); @@ -66,41 +66,101 @@ const seriesProcessor: SeriesProcessor<'bar'> = (params, dataset) => { const completedSeries: { [id: string]: DefaultizedBarSeriesType & { + visibleStackedData: [number, number][]; stackedData: [number, number][]; }; } = {}; stackingGroups.forEach((stackingGroup) => { const { ids, stackingOffset, stackingOrder } = stackingGroup; - // Get stacked values, and derive the domain - const stackedSeries = d3Stack, SeriesId>() - .keys( - ids.map((id) => { - // Use dataKey if needed and available - const dataKey = series[id].dataKey; - return series[id].data === undefined && dataKey !== undefined ? dataKey : id; - }), - ) + const keys = ids.map((id) => { + // Use dataKey if needed and available + const dataKey = series[id].dataKey; + return series[id].data === undefined && dataKey !== undefined ? dataKey : id; + }); + + const stackedData = d3Stack, SeriesId>() + .keys(keys) .value((d, key) => d[key] ?? 0) // defaultize null value to 0 .order(stackingOrder) .offset(stackingOffset)(d3Dataset); + // We sort the keys based on the original stacking order to ensure consistency + const idOrder = stackedData.sort((a, b) => a.index - b.index).map((s) => s.key); + + // Compute visible stacked data + const visibleStackedData = d3Stack, SeriesId>() + .keys(idOrder) + .value((d, key) => { + const keyIndex = keys.indexOf(key); + const seriesId = ids[keyIndex]; + + if (!isIdentifierVisible?.(`${seriesId}`)) { + // For hidden series, return 0 so they don't contribute to the stack + return 0; + } + return d[key] ?? 0; + }) + .offset(stackingOffset)(d3Dataset); + + // Post-process visibleStackedData to fix positions for hidden series + // Hidden series should collapse to the cumulative position of visible series before them + visibleStackedData.forEach((layer, layerIndex) => { + const key = idOrder[layerIndex]; + const keyIndex = keys.indexOf(key); + const seriesId = ids[keyIndex]; + + if (!isIdentifierVisible?.(`${seriesId}`)) { + layer.forEach((point, pointIndex) => { + // Get the original value to determine if it's negative or positive + const originalValue = d3Dataset[pointIndex]?.[key] ?? 0; + const isNegative = originalValue < 0; + + // Calculate the cumulative sum of all visible series before this one + // Only accumulate values with the same sign + let cumulativeSum = 0; + for (let i = 0; i < layerIndex; i += 1) { + const prevKey = idOrder[i]; + const prevKeyIndex = keys.indexOf(prevKey); + const prevSeriesId = ids[prevKeyIndex]; + + if (isIdentifierVisible?.(`${prevSeriesId}`)) { + const value = d3Dataset[pointIndex]?.[prevKey] ?? 0; + const isPrevNegative = value < 0; + + // Only accumulate if both have the same sign + if (isNegative === isPrevNegative) { + cumulativeSum += value; + } + } + } + + // Set both start and end to the cumulative position (zero height/width) + point[0] = cumulativeSum; + point[1] = cumulativeSum; + }); + } + }); + ids.forEach((id, index) => { const dataKey = series[id].dataKey; + const data = dataKey + ? dataset!.map((d) => { + const value = d[dataKey]; + return typeof value === 'number' ? value : null; + }) + : series[id].data!; + const hidden = !isIdentifierVisible?.(`${id}`); completedSeries[id] = { layout: 'vertical', labelMarkType: 'square', minBarSize: 0, valueFormatter: series[id].valueFormatter ?? barValueFormatter, ...series[id], - data: dataKey - ? dataset!.map((data) => { - const value = data[dataKey]; - - return typeof value === 'number' ? value : null; - }) - : series[id].data!, - stackedData: stackedSeries[index].map(([a, b]) => [a, b]), + data, + hidden, + stackedData: stackedData[index] as [number, number][], + visibleStackedData: visibleStackedData[index] as [number, number][], }; }); }); diff --git a/packages/x-charts/src/BarChart/seriesConfig/bar/tooltipPosition.ts b/packages/x-charts/src/BarChart/seriesConfig/bar/tooltipPosition.ts index bcab834da07cd..eebb268ed383d 100644 --- a/packages/x-charts/src/BarChart/seriesConfig/bar/tooltipPosition.ts +++ b/packages/x-charts/src/BarChart/seriesConfig/bar/tooltipPosition.ts @@ -25,6 +25,7 @@ const tooltipItemPositionGetter: TooltipItemPositionGetter<'bar'> = (params) => dataIndex: identifier.dataIndex, numberOfGroups: series.bar.stackingGroups.length, groupIndex: series.bar.stackingGroups.findIndex((group) => group.ids.includes(itemSeries.id)), + isSeriesVisible: true, }); if (dimensions == null) { diff --git a/packages/x-charts/src/BarChart/types.ts b/packages/x-charts/src/BarChart/types.ts index 779f8c84e4444..f942afbd3377c 100644 --- a/packages/x-charts/src/BarChart/types.ts +++ b/packages/x-charts/src/BarChart/types.ts @@ -24,6 +24,7 @@ export interface ProcessedBarData extends AnimationData { color: string; value: number | null; maskId: string; + hidden: boolean; } export interface MaskData extends AnimationData { diff --git a/packages/x-charts/src/BarChart/useBarPlotData.ts b/packages/x-charts/src/BarChart/useBarPlotData.ts index b601cf29a5f6b..a114eff65985f 100644 --- a/packages/x-charts/src/BarChart/useBarPlotData.ts +++ b/packages/x-charts/src/BarChart/useBarPlotData.ts @@ -75,6 +75,7 @@ export function useBarPlotData( dataIndex, numberOfGroups: stackingGroups.length, groupIndex, + isSeriesVisible: !series[seriesId].hidden, }); if (barDimensions == null) { @@ -86,6 +87,8 @@ export function useBarPlotData( const result = { seriesId, dataIndex, + layout: series[seriesId].layout, + hidden: series[seriesId].hidden, ...barDimensions, color: colorGetter(dataIndex), value: series[seriesId].data[dataIndex], diff --git a/packages/x-charts/src/LineChart/seriesConfig/seriesProcessor.ts b/packages/x-charts/src/LineChart/seriesConfig/seriesProcessor.ts index 8fb06dd0bfd28..1b66d9af954c8 100644 --- a/packages/x-charts/src/LineChart/seriesConfig/seriesProcessor.ts +++ b/packages/x-charts/src/LineChart/seriesConfig/seriesProcessor.ts @@ -1,6 +1,6 @@ import { stack as d3Stack } from '@mui/x-charts-vendor/d3-shape'; import { warnOnce } from '@mui/x-internals/warning'; -import { getStackingGroups } from '../../internals/stackSeries'; +import { getStackingGroups, StackOrder } from '../../internals/stackSeries'; import { type ChartSeriesDefaultized, type DatasetElementType, @@ -8,8 +8,12 @@ import { } from '../../models/seriesType/config'; import { type SeriesId } from '../../models/seriesType/common'; import { type SeriesProcessor } from '../../internals/plugins/models'; +import type { DefaultizedLineSeriesType } from '../../models'; -const seriesProcessor: SeriesProcessor<'line'> = (params, dataset) => { +const lineValueFormatter = ((v) => + v == null ? '' : v.toLocaleString()) as DefaultizedLineSeriesType['valueFormatter']; + +const seriesProcessor: SeriesProcessor<'line'> = (params, dataset, isIdentifierVisible) => { const { seriesOrder, series } = params; const stackingGroups = getStackingGroups({ ...params, defaultStrategy: { stackOffset: 'none' } }); @@ -65,36 +69,94 @@ const seriesProcessor: SeriesProcessor<'line'> = (params, dataset) => { const completedSeries: Record> = {}; stackingGroups.forEach((stackingGroup) => { - // Get stacked values, and derive the domain - const { ids, stackingOrder, stackingOffset } = stackingGroup; - const stackedSeries = d3Stack, SeriesId>() - .keys( - ids.map((id) => { - // Use dataKey if needed and available - const dataKey = series[id].dataKey; - return series[id].data === undefined && dataKey !== undefined ? dataKey : id; - }), - ) + const { ids, stackingOffset, stackingOrder } = stackingGroup; + const keys = ids.map((id) => { + // Use dataKey if needed and available + const dataKey = series[id].dataKey; + return series[id].data === undefined && dataKey !== undefined ? dataKey : id; + }); + + const stackedData = d3Stack, SeriesId>() + .keys(keys) .value((d, key) => d[key] ?? 0) // defaultize null value to 0 .order(stackingOrder) .offset(stackingOffset)(d3Dataset); + // We sort the keys based on the original stacking order to ensure consistency + const idOrder = stackedData.sort((a, b) => a.index - b.index).map((s) => s.key); + + // Compute visible stacked data + const visibleStackedData = d3Stack, SeriesId>() + .keys(idOrder) + .value((d, key) => { + const keyIndex = keys.indexOf(key); + const seriesId = ids[keyIndex]; + + if (!isIdentifierVisible?.(`${seriesId}`)) { + // For hidden series, return 0 so they don't contribute to the stack + return 0; + } + return d[key] ?? 0; + }) + .order(StackOrder.none) + .offset(stackingOffset)(d3Dataset); + + // Post-process visibleStackedData to fix positions for hidden series + // Hidden series should collapse to the cumulative position of visible series before them + visibleStackedData.forEach((layer, layerIndex) => { + const key = idOrder[layerIndex]; + const keyIndex = keys.indexOf(key); + const seriesId = ids[keyIndex]; + + if (!isIdentifierVisible?.(`${seriesId}`)) { + layer.forEach((point, pointIndex) => { + // Get the original value to determine if it's negative or positive + const originalValue = d3Dataset[pointIndex]?.[key] ?? 0; + const isNegative = originalValue < 0; + + // Calculate the cumulative sum of all visible series before this one + // Only accumulate values with the same sign + let cumulativeSum = 0; + for (let i = 0; i < layerIndex; i += 1) { + const prevKey = idOrder[i]; + const prevKeyIndex = keys.indexOf(prevKey); + const prevSeriesId = ids[prevKeyIndex]; + + if (isIdentifierVisible?.(`${prevSeriesId}`)) { + const value = d3Dataset[pointIndex]?.[prevKey] ?? 0; + const isPrevNegative = value < 0; + + // Only accumulate if both have the same sign + if (isNegative === isPrevNegative) { + cumulativeSum += value; + } + } + } + + // Set both start and end to the cumulative position (zero height/width) + point[0] = cumulativeSum; + point[1] = cumulativeSum; + }); + } + }); + ids.forEach((id, index) => { const dataKey = series[id].dataKey; + const data = dataKey + ? dataset!.map((d) => { + const value = d[dataKey]; + return typeof value === 'number' ? value : null; + }) + : series[id].data!; + const hidden = !isIdentifierVisible?.(`${id}`); completedSeries[id] = { labelMarkType: 'line', ...series[id], - data: dataKey - ? dataset!.map((data) => { - const value = data[dataKey]; - - return typeof value === 'number' ? value : null; - }) - : series[id].data!, - stackedData: stackedSeries[index].map(([a, b]) => [a, b]), - valueFormatter: - series[id]?.valueFormatter ?? - ((v: number | null) => (v == null ? '' : v.toLocaleString())), + data, + valueFormatter: series[id].valueFormatter ?? lineValueFormatter, + hidden, + stackedData: stackedData[index] as [number, number][], + visibleStackedData: visibleStackedData[index] as [number, number][], }; }); }); diff --git a/packages/x-charts/src/LineChart/useAreaPlotData.ts b/packages/x-charts/src/LineChart/useAreaPlotData.ts index 9ca6ecfe886d9..a303a4064ec97 100644 --- a/packages/x-charts/src/LineChart/useAreaPlotData.ts +++ b/packages/x-charts/src/LineChart/useAreaPlotData.ts @@ -43,6 +43,7 @@ export function useAreaPlotData( const { xAxisId = defaultXAxisId, yAxisId = defaultYAxisId, + visibleStackedData, stackedData, data, connectNulls, @@ -94,11 +95,11 @@ export function useAreaPlotData( xData?.flatMap((x, index) => { const nullData = data[index] == null; if (shouldExpand) { - const rep = [{ x, y: stackedData[index], nullData, isExtension: false }]; + const rep = [{ x, y: visibleStackedData[index], nullData, isExtension: false }]; if (!nullData && (index === 0 || data[index - 1] == null)) { rep.unshift({ x: (xScale(x) ?? 0) - (xScale.step() - xScale.bandwidth()) / 2, - y: stackedData[index], + y: visibleStackedData[index], nullData, isExtension: true, }); @@ -106,14 +107,14 @@ export function useAreaPlotData( if (!nullData && (index === data.length - 1 || data[index + 1] == null)) { rep.push({ x: (xScale(x) ?? 0) + (xScale.step() + xScale.bandwidth()) / 2, - y: stackedData[index], + y: visibleStackedData[index], nullData, isExtension: true, }); } return rep; } - return { x, y: stackedData[index], nullData }; + return { x, y: visibleStackedData[index], nullData }; }) ?? []; const d3Data = connectNulls ? formattedData.filter((d) => !d.nullData) : formattedData; diff --git a/packages/x-charts/src/LineChart/useLinePlotData.ts b/packages/x-charts/src/LineChart/useLinePlotData.ts index 30303ce5140f8..307984420bd63 100644 --- a/packages/x-charts/src/LineChart/useLinePlotData.ts +++ b/packages/x-charts/src/LineChart/useLinePlotData.ts @@ -15,6 +15,7 @@ interface LinePlotDataPoint { seriesId: SeriesId; color: string; gradientId?: string; + hidden: boolean; } export function useLinePlotData( @@ -44,6 +45,7 @@ export function useLinePlotData( xAxisId = defaultXAxisId, yAxisId = defaultYAxisId, stackedData, + visibleStackedData, data, connectNulls, curve, @@ -93,11 +95,11 @@ export function useLinePlotData( xData?.flatMap((x, index) => { const nullData = data[index] == null; if (shouldExpand) { - const rep = [{ x, y: stackedData[index], nullData, isExtension: false }]; + const rep = [{ x, y: visibleStackedData[index], nullData, isExtension: false }]; if (!nullData && (index === 0 || data[index - 1] == null)) { rep.unshift({ x: (xScale(x) ?? 0) - (xScale.step() - xScale.bandwidth()) / 2, - y: stackedData[index], + y: visibleStackedData[index], nullData, isExtension: true, }); @@ -105,17 +107,18 @@ export function useLinePlotData( if (!nullData && (index === data.length - 1 || data[index + 1] == null)) { rep.push({ x: (xScale(x) ?? 0) + (xScale.step() + xScale.bandwidth()) / 2, - y: stackedData[index], + y: visibleStackedData[index], nullData, isExtension: true, }); } return rep; } - return { x, y: stackedData[index], nullData }; + return { x, y: visibleStackedData[index], nullData }; }) ?? []; const d3Data = connectNulls ? formattedData.filter((d) => !d.nullData) : formattedData; + const hidden = series[seriesId].hidden; const linePath = d3Line<{ x: any; @@ -125,7 +128,13 @@ export function useLinePlotData( }>() .x((d) => (d.isExtension ? d.x : xPosition(d.x))) .defined((d) => connectNulls || !d.nullData || !!d.isExtension) - .y((d) => yScale(d.y[1])!); + .y((d) => { + if (hidden) { + return yScale(yScale.domain()[0] as number)!; + } + + return yScale(d.y[1])!; + }); const d = linePath.curve(getCurveFactory(curve))(d3Data) || ''; linePlotData.push({ @@ -133,6 +142,7 @@ export function useLinePlotData( gradientId, d, seriesId, + hidden: series[seriesId].hidden, }); } } diff --git a/packages/x-charts/src/PieChart/seriesConfig/seriesProcessor.ts b/packages/x-charts/src/PieChart/seriesConfig/seriesProcessor.ts index 030f16532d713..d8fed358dbb1e 100644 --- a/packages/x-charts/src/PieChart/seriesConfig/seriesProcessor.ts +++ b/packages/x-charts/src/PieChart/seriesConfig/seriesProcessor.ts @@ -22,38 +22,73 @@ const getSortingComparator = (comparator: ChartsPieSorting = 'none') => { } }; -const seriesProcessor: SeriesProcessor<'pie'> = (params) => { +const seriesProcessor: SeriesProcessor<'pie'> = (params, dataset, isIdentifierVisible) => { const { seriesOrder, series } = params; const defaultizedSeries: Record> = {}; seriesOrder.forEach((seriesId) => { - const arcs = d3Pie() + // Filter out hidden data points for arc calculation + const visibleData = series[seriesId].data.filter((piePoint, index) => { + const itemId = piePoint.id ?? `auto-generated-pie-id-${seriesId}-${index}`; + return isIdentifierVisible?.([seriesId, itemId]); + }); + + const visibleArcs = d3Pie() .startAngle(deg2rad(series[seriesId].startAngle ?? 0)) .endAngle(deg2rad(series[seriesId].endAngle ?? 360)) .padAngle(deg2rad(series[seriesId].paddingAngle ?? 0)) .sortValues(getSortingComparator(series[seriesId].sortingValues ?? 'none'))( - series[seriesId].data.map((piePoint) => piePoint.value), + visibleData.map((piePoint) => piePoint.value), ); + // Map arcs back to original data, maintaining original indices + let visibleIndex = 0; defaultizedSeries[seriesId] = { labelMarkType: 'circle', valueFormatter: (item: PieValueType) => item.value.toLocaleString(), ...series[seriesId], - data: series[seriesId].data - .map((item, index) => ({ + data: series[seriesId].data.map((item, index) => { + const itemId = item.id ?? `auto-generated-pie-id-${seriesId}-${index}`; + const isHidden = !isIdentifierVisible?.([seriesId, itemId]); + let arcData; + + if (isHidden) { + // For hidden items, create a zero-size arc starting at the previous visible arc's end angle + // and ending at the same angle + const startAngle = + visibleIndex > 0 + ? visibleArcs[visibleIndex - 1].endAngle + : deg2rad(series[seriesId].startAngle ?? 0); + + arcData = { + startAngle, + endAngle: startAngle, + padAngle: 0, + value: item.value, + index, + }; + } else { + arcData = visibleArcs[visibleIndex]; + visibleIndex += 1; + } + + const processedItem = { ...item, - id: item.id ?? `auto-generated-pie-id-${seriesId}-${index}`, - ...arcs[index], - })) - .map((item, index) => ({ + id: itemId, + hidden: isHidden, + ...arcData, + }; + + return { labelMarkType: 'circle', - ...item, + ...processedItem, formattedValue: series[seriesId].valueFormatter?.( - { ...item, label: getLabel(item.label, 'arc') }, + { ...processedItem, label: getLabel(processedItem.label, 'arc') }, { dataIndex: index }, - ) ?? item.value.toLocaleString(), - })), + ) ?? processedItem.value.toLocaleString(), + }; + }), }; }); diff --git a/packages/x-charts/src/RadarChart/seriesConfig/seriesProcessor.ts b/packages/x-charts/src/RadarChart/seriesConfig/seriesProcessor.ts index dd7efe6d3119f..214fda0b956c5 100644 --- a/packages/x-charts/src/RadarChart/seriesConfig/seriesProcessor.ts +++ b/packages/x-charts/src/RadarChart/seriesConfig/seriesProcessor.ts @@ -5,18 +5,20 @@ import type { SeriesId } from '../../models/seriesType/common'; const defaultRadarValueFormatter: DefaultizedRadarSeriesType['valueFormatter'] = (v) => v == null ? '' : v.toLocaleString(); -const seriesProcessor: SeriesProcessor<'radar'> = (params, _) => { +const seriesProcessor: SeriesProcessor<'radar'> = (params, _, isIdentifierVisible) => { const { seriesOrder, series: seriesMap } = params; const completedSeries: Record = {}; seriesOrder.forEach((seriesId) => { const series = seriesMap[seriesId]; + const hidden = !isIdentifierVisible?.(`${seriesId}`); completedSeries[seriesId] = { labelMarkType: 'square', ...series, valueFormatter: series.valueFormatter ?? defaultRadarValueFormatter, + hidden, }; }); diff --git a/packages/x-charts/src/ScatterChart/seriesConfig/seriesProcessor.ts b/packages/x-charts/src/ScatterChart/seriesConfig/seriesProcessor.ts index afaa32dd9efb9..3beac2bb258dd 100644 --- a/packages/x-charts/src/ScatterChart/seriesConfig/seriesProcessor.ts +++ b/packages/x-charts/src/ScatterChart/seriesConfig/seriesProcessor.ts @@ -1,7 +1,11 @@ import { type ScatterValueType } from '../../models'; import { type SeriesProcessor } from '../../internals/plugins/models'; -const seriesProcessor: SeriesProcessor<'scatter'> = ({ series, seriesOrder }, dataset) => { +const seriesProcessor: SeriesProcessor<'scatter'> = ( + { series, seriesOrder }, + dataset, + isIdentifierVisible, +) => { const completeSeries = Object.fromEntries( Object.entries(series).map(([seriesId, seriesData]) => { const datasetKeys = seriesData?.datasetKeys; @@ -41,6 +45,7 @@ const seriesProcessor: SeriesProcessor<'scatter'> = ({ series, seriesOrder }, da ...seriesData?.preview, }, data, + hidden: !isIdentifierVisible?.(`${seriesId}`), valueFormatter: seriesData.valueFormatter ?? ((v) => v && `(${v.x}, ${v.y})`), }, ]; diff --git a/packages/x-charts/src/internals/getBarDimensions.ts b/packages/x-charts/src/internals/getBarDimensions.ts index 20a2c48c8d116..4c2e7a23fbb98 100644 --- a/packages/x-charts/src/internals/getBarDimensions.ts +++ b/packages/x-charts/src/internals/getBarDimensions.ts @@ -1,6 +1,7 @@ import type { ScaleName, ChartsXAxisProps, ChartsYAxisProps } from '../models'; import type { ComputedAxis } from '../models/axis'; import type { ChartSeriesDefaultized } from '../models/seriesType/config'; +import { findMinMax } from './findMinMax'; import { getBandSize } from './getBandSize'; function shouldInvertStartCoordinate(verticalLayout: boolean, baseValue: number, reverse: boolean) { @@ -19,6 +20,7 @@ export function getBarDimensions(params: { dataIndex: number; numberOfGroups: number; groupIndex: number; + isSeriesVisible: boolean; }) { const { verticalLayout, @@ -28,6 +30,7 @@ export function getBarDimensions(params: { dataIndex, numberOfGroups, groupIndex, + isSeriesVisible, } = params; const baseScaleConfig = (verticalLayout ? xAxisConfig : yAxisConfig) as ComputedAxis<'band'>; @@ -50,17 +53,31 @@ export function getBarDimensions(params: { return null; } - const values = series.stackedData[dataIndex]; - const valueCoordinates = values.map((v) => (verticalLayout ? yScale(v)! : xScale(v)!)); + const visibleValues = series.visibleStackedData[dataIndex]; + const visibleValueCoordinates = visibleValues.map((v) => + verticalLayout ? yScale(v)! : xScale(v)!, + ); + + const [minValueCoord, maxValueCoord] = findMinMax(visibleValueCoordinates).map((v) => + Math.round(v), + ); - const minValueCoord = Math.round(Math.min(...valueCoordinates)); - const maxValueCoord = Math.round(Math.max(...valueCoordinates)); + let barSize = 0; + if (seriesValue !== 0) { + if (isSeriesVisible) { + barSize = Math.max(series.minBarSize, maxValueCoord - minValueCoord); + } + } - const barSize = - seriesValue === 0 ? 0 : Math.max(series.minBarSize, maxValueCoord - minValueCoord); - const startCoordinate = shouldInvertStartCoordinate(verticalLayout, seriesValue, reverse) - ? maxValueCoord - barSize - : minValueCoord; + const shouldInvert = shouldInvertStartCoordinate(verticalLayout, seriesValue, reverse); + + let startCoordinate = 0; + + if (shouldInvert) { + startCoordinate = maxValueCoord - barSize; + } else { + startCoordinate = minValueCoord; + } return { x: verticalLayout ? xScale(baseValue)! + barOffset : startCoordinate, diff --git a/packages/x-charts/src/internals/index.ts b/packages/x-charts/src/internals/index.ts index e7c4c3f9a97a5..b36a99991bd79 100644 --- a/packages/x-charts/src/internals/index.ts +++ b/packages/x-charts/src/internals/index.ts @@ -33,6 +33,7 @@ export * from './plugins/featurePlugins/useChartCartesianAxis'; export * from './plugins/featurePlugins/useChartPolarAxis'; export * from './plugins/featurePlugins/useChartInteraction'; export * from './plugins/featurePlugins/useChartHighlight'; +export * from './plugins/featurePlugins/useChartVisibilityManager'; export * from './plugins/featurePlugins/useChartKeyboardNavigation'; export * from './plugins/featurePlugins/useChartClosestPoint'; export * from './plugins/featurePlugins/useChartBrush'; diff --git a/packages/x-charts/src/internals/plugins/corePlugins/useChartSeries/processSeries.ts b/packages/x-charts/src/internals/plugins/corePlugins/useChartSeries/processSeries.ts index 90149ffd045b1..48218446cfef9 100644 --- a/packages/x-charts/src/internals/plugins/corePlugins/useChartSeries/processSeries.ts +++ b/packages/x-charts/src/internals/plugins/corePlugins/useChartSeries/processSeries.ts @@ -8,6 +8,7 @@ import { type ProcessedSeries, type SeriesLayout, } from './useChartSeries.types'; +import type { IsIdentifierVisibleFunction } from '../../featurePlugins/useChartVisibilityManager'; /** * This method groups series by type and adds defaultized values such as the ids and colors. @@ -64,6 +65,7 @@ export const applySeriesProcessors = ( defaultizedSeries: DefaultizedSeriesGroups, seriesConfig: ChartSeriesConfig, dataset?: Readonly, + isIdentifierVisible?: IsIdentifierVisibleFunction, ): ProcessedSeries => { const processedSeries: ProcessedSeries = {}; @@ -71,7 +73,8 @@ export const applySeriesProcessors = ( (Object.keys(seriesConfig) as TSeriesType[]).forEach((type) => { const group = defaultizedSeries[type]; if (group !== undefined) { - processedSeries[type] = seriesConfig[type]?.seriesProcessor?.(group, dataset) ?? group; + processedSeries[type] = + seriesConfig[type]?.seriesProcessor?.(group, dataset, isIdentifierVisible) ?? group; } }); diff --git a/packages/x-charts/src/internals/plugins/corePlugins/useChartSeries/useChartSeries.selectors.ts b/packages/x-charts/src/internals/plugins/corePlugins/useChartSeries/useChartSeries.selectors.ts index 1ed7c6bfada6a..6499eb7966734 100644 --- a/packages/x-charts/src/internals/plugins/corePlugins/useChartSeries/useChartSeries.selectors.ts +++ b/packages/x-charts/src/internals/plugins/corePlugins/useChartSeries/useChartSeries.selectors.ts @@ -2,7 +2,8 @@ import { createSelectorMemoized, createSelector } from '@mui/x-internals/store'; import { type ChartRootSelector } from '../../utils/selectors'; import { type UseChartSeriesSignature } from './useChartSeries.types'; import { applySeriesLayout, applySeriesProcessors } from './processSeries'; -import { selectorChartDrawingArea } from '../useChartDimensions/useChartDimensions.selectors'; +import { selectorChartDrawingArea } from '../useChartDimensions'; +import { selectorIsIdentifierVisibleGetter } from '../../featurePlugins/useChartVisibilityManager'; export const selectorChartSeriesState: ChartRootSelector = (state) => state.series; @@ -35,8 +36,14 @@ export const selectorChartSeriesProcessed = createSelectorMemoized( selectorChartDefaultizedSeries, selectorChartSeriesConfig, selectorChartDataset, - function selectorChartSeriesProcessed(defaultizedSeries, seriesConfig, dataset) { - return applySeriesProcessors(defaultizedSeries, seriesConfig, dataset); + selectorIsIdentifierVisibleGetter, + function selectorChartSeriesProcessed( + defaultizedSeries, + seriesConfig, + dataset, + isIdentifierVisible, + ) { + return applySeriesProcessors(defaultizedSeries, seriesConfig, dataset, isIdentifierVisible.get); }, ); diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.helpers.test.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.helpers.test.ts index 04a3c93e126c2..40e1cba29cb78 100644 --- a/packages/x-charts/src/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.helpers.test.ts +++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.helpers.test.ts @@ -7,6 +7,7 @@ import { const barSeries = { type: 'bar' as const, stackedData: [], + visibleStackedData: [], valueFormatter: () => '', color: '', layout: 'horizontal' as const, @@ -16,6 +17,7 @@ const barSeries = { const lineSeries = { type: 'line' as const, stackedData: [], + visibleStackedData: [], valueFormatter: () => '', color: '', }; @@ -26,11 +28,13 @@ const seriesSingleType: ProcessedSeries<'bar'> = { b: { data: [1, 2], id: 'b', + hidden: false, ...barSeries, }, a: { data: [1, 2], id: 'a', + hidden: false, ...barSeries, }, }, @@ -45,11 +49,13 @@ const seriesMultipleTypes: ProcessedSeries<'bar' | 'line'> = { b: { data: [1, 2], id: 'b', + hidden: false, ...barSeries, }, a: { data: [1, 2], id: 'a', + hidden: false, ...barSeries, }, }, @@ -61,11 +67,13 @@ const seriesMultipleTypes: ProcessedSeries<'bar' | 'line'> = { b: { data: [1, 2], id: 'b', + hidden: false, ...lineSeries, }, a: { data: [1, 2], id: 'a', + hidden: false, ...lineSeries, }, }, diff --git a/packages/x-charts/src/internals/plugins/models/seriesConfig/seriesProcessor.types.ts b/packages/x-charts/src/internals/plugins/models/seriesConfig/seriesProcessor.types.ts index ec32269914d57..75d20dcf582d4 100644 --- a/packages/x-charts/src/internals/plugins/models/seriesConfig/seriesProcessor.types.ts +++ b/packages/x-charts/src/internals/plugins/models/seriesConfig/seriesProcessor.types.ts @@ -6,6 +6,7 @@ import type { } from '../../../../models/seriesType/config'; import type { SeriesId } from '../../../../models/seriesType/common'; import type { StackingGroupsType } from '../../../stackSeries'; +import type { IsIdentifierVisibleFunction } from '../../featurePlugins/useChartVisibilityManager'; export type SeriesProcessorParams = { series: Record; @@ -24,4 +25,5 @@ export type SeriesProcessorResult = { export type SeriesProcessor = ( params: SeriesProcessorParams, dataset?: Readonly, + isIdentifierVisible?: IsIdentifierVisibleFunction, ) => SeriesProcessorResult; diff --git a/packages/x-charts/src/plugins/index.ts b/packages/x-charts/src/plugins/index.ts index 79c67afb91c9d..d4430c9f6c38b 100644 --- a/packages/x-charts/src/plugins/index.ts +++ b/packages/x-charts/src/plugins/index.ts @@ -28,3 +28,9 @@ export { useChartZAxis, type UseChartZAxisSignature, } from '../internals/plugins/featurePlugins/useChartZAxis'; +export { + useChartVisibilityManager, + type UseChartVisibilityManagerSignature, + type UseChartVisibilityManagerInstance, + type UseChartVisibilityManagerParameters, +} from '../internals/plugins/featurePlugins/useChartVisibilityManager'; From 7fde6c400361e7e5874cb9b21c1cbb080582dcf8 Mon Sep 17 00:00:00 2001 From: Jose Quintas Date: Fri, 5 Dec 2025 13:01:39 +0100 Subject: [PATCH 4/9] exports --- scripts/x-charts-pro.exports.json | 4 ++++ scripts/x-charts.exports.json | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/scripts/x-charts-pro.exports.json b/scripts/x-charts-pro.exports.json index 7707aea548f7d..c5043d78cbc2e 100644 --- a/scripts/x-charts-pro.exports.json +++ b/scripts/x-charts-pro.exports.json @@ -580,6 +580,10 @@ { "name": "UseChartProZoomSignature", "kind": "TypeAlias" }, { "name": "useChartRootRef", "kind": "Function" }, { "name": "useChartsLocalization", "kind": "Variable" }, + { "name": "useChartVisibilityManager", "kind": "Variable" }, + { "name": "UseChartVisibilityManagerInstance", "kind": "Interface" }, + { "name": "UseChartVisibilityManagerParameters", "kind": "Interface" }, + { "name": "UseChartVisibilityManagerSignature", "kind": "TypeAlias" }, { "name": "useChartZAxis", "kind": "Variable" }, { "name": "UseChartZAxisSignature", "kind": "TypeAlias" }, { "name": "useDataset", "kind": "Function" }, diff --git a/scripts/x-charts.exports.json b/scripts/x-charts.exports.json index ad37dd466ca12..9ddd4dc800d46 100644 --- a/scripts/x-charts.exports.json +++ b/scripts/x-charts.exports.json @@ -456,6 +456,10 @@ { "name": "UseChartPolarAxisSignature", "kind": "TypeAlias" }, { "name": "useChartRootRef", "kind": "Function" }, { "name": "useChartsLocalization", "kind": "Variable" }, + { "name": "useChartVisibilityManager", "kind": "Variable" }, + { "name": "UseChartVisibilityManagerInstance", "kind": "Interface" }, + { "name": "UseChartVisibilityManagerParameters", "kind": "Interface" }, + { "name": "UseChartVisibilityManagerSignature", "kind": "TypeAlias" }, { "name": "useChartZAxis", "kind": "Variable" }, { "name": "UseChartZAxisSignature", "kind": "TypeAlias" }, { "name": "useDataset", "kind": "Function" }, From 5b3868c606b0e68555c3f428b2cdaebc5aeca587 Mon Sep 17 00:00:00 2001 From: Jose Quintas Date: Fri, 5 Dec 2025 16:40:03 +0100 Subject: [PATCH 5/9] remove cumulative positioning --- .../seriesConfig/bar/seriesProcessor.ts | 39 ------------------- .../LineChart/seriesConfig/seriesProcessor.ts | 39 ------------------- 2 files changed, 78 deletions(-) diff --git a/packages/x-charts/src/BarChart/seriesConfig/bar/seriesProcessor.ts b/packages/x-charts/src/BarChart/seriesConfig/bar/seriesProcessor.ts index 158f372ceec26..c6c0b2e03e6c6 100644 --- a/packages/x-charts/src/BarChart/seriesConfig/bar/seriesProcessor.ts +++ b/packages/x-charts/src/BarChart/seriesConfig/bar/seriesProcessor.ts @@ -103,45 +103,6 @@ const seriesProcessor: SeriesProcessor<'bar'> = (params, dataset, isIdentifierVi }) .offset(stackingOffset)(d3Dataset); - // Post-process visibleStackedData to fix positions for hidden series - // Hidden series should collapse to the cumulative position of visible series before them - visibleStackedData.forEach((layer, layerIndex) => { - const key = idOrder[layerIndex]; - const keyIndex = keys.indexOf(key); - const seriesId = ids[keyIndex]; - - if (!isIdentifierVisible?.(`${seriesId}`)) { - layer.forEach((point, pointIndex) => { - // Get the original value to determine if it's negative or positive - const originalValue = d3Dataset[pointIndex]?.[key] ?? 0; - const isNegative = originalValue < 0; - - // Calculate the cumulative sum of all visible series before this one - // Only accumulate values with the same sign - let cumulativeSum = 0; - for (let i = 0; i < layerIndex; i += 1) { - const prevKey = idOrder[i]; - const prevKeyIndex = keys.indexOf(prevKey); - const prevSeriesId = ids[prevKeyIndex]; - - if (isIdentifierVisible?.(`${prevSeriesId}`)) { - const value = d3Dataset[pointIndex]?.[prevKey] ?? 0; - const isPrevNegative = value < 0; - - // Only accumulate if both have the same sign - if (isNegative === isPrevNegative) { - cumulativeSum += value; - } - } - } - - // Set both start and end to the cumulative position (zero height/width) - point[0] = cumulativeSum; - point[1] = cumulativeSum; - }); - } - }); - ids.forEach((id, index) => { const dataKey = series[id].dataKey; const data = dataKey diff --git a/packages/x-charts/src/LineChart/seriesConfig/seriesProcessor.ts b/packages/x-charts/src/LineChart/seriesConfig/seriesProcessor.ts index 1b66d9af954c8..63e2e6255407a 100644 --- a/packages/x-charts/src/LineChart/seriesConfig/seriesProcessor.ts +++ b/packages/x-charts/src/LineChart/seriesConfig/seriesProcessor.ts @@ -101,45 +101,6 @@ const seriesProcessor: SeriesProcessor<'line'> = (params, dataset, isIdentifierV .order(StackOrder.none) .offset(stackingOffset)(d3Dataset); - // Post-process visibleStackedData to fix positions for hidden series - // Hidden series should collapse to the cumulative position of visible series before them - visibleStackedData.forEach((layer, layerIndex) => { - const key = idOrder[layerIndex]; - const keyIndex = keys.indexOf(key); - const seriesId = ids[keyIndex]; - - if (!isIdentifierVisible?.(`${seriesId}`)) { - layer.forEach((point, pointIndex) => { - // Get the original value to determine if it's negative or positive - const originalValue = d3Dataset[pointIndex]?.[key] ?? 0; - const isNegative = originalValue < 0; - - // Calculate the cumulative sum of all visible series before this one - // Only accumulate values with the same sign - let cumulativeSum = 0; - for (let i = 0; i < layerIndex; i += 1) { - const prevKey = idOrder[i]; - const prevKeyIndex = keys.indexOf(prevKey); - const prevSeriesId = ids[prevKeyIndex]; - - if (isIdentifierVisible?.(`${prevSeriesId}`)) { - const value = d3Dataset[pointIndex]?.[prevKey] ?? 0; - const isPrevNegative = value < 0; - - // Only accumulate if both have the same sign - if (isNegative === isPrevNegative) { - cumulativeSum += value; - } - } - } - - // Set both start and end to the cumulative position (zero height/width) - point[0] = cumulativeSum; - point[1] = cumulativeSum; - }); - } - }); - ids.forEach((id, index) => { const dataKey = series[id].dataKey; const data = dataKey From 8e289c59fbd535f2bde1045e4b9d1e4e2411a2d8 Mon Sep 17 00:00:00 2001 From: Jose Quintas Date: Mon, 8 Dec 2025 12:02:49 +0100 Subject: [PATCH 6/9] update plugins --- docs/data/charts/plugins/plugins.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/data/charts/plugins/plugins.md b/docs/data/charts/plugins/plugins.md index 89699cf58f67a..26ece40732aab 100644 --- a/docs/data/charts/plugins/plugins.md +++ b/docs/data/charts/plugins/plugins.md @@ -75,6 +75,7 @@ For example, the `useChartClosestPoint` has the `useChartCartesianAxis` as a dep | `useChartHighlight` | | | | `useChartInteraction` | | | | `useChartClosestPoint` | `useChartCartesianAxis` | `useChartInteraction`,
`useChartHighlight` | +| `useChartVisibilityManager` | | | | `useChartZAxis` | | | | `useChartBrush` | | | | `useChartProExport` | | | From b1295f85ac39924a27b5f930439403f52d662ada Mon Sep 17 00:00:00 2001 From: Jose Quintas Date: Mon, 8 Dec 2025 12:12:37 +0100 Subject: [PATCH 7/9] var name --- packages/x-charts/src/internals/getBarDimensions.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/x-charts/src/internals/getBarDimensions.ts b/packages/x-charts/src/internals/getBarDimensions.ts index 4c2e7a23fbb98..62a3b7a64a5d1 100644 --- a/packages/x-charts/src/internals/getBarDimensions.ts +++ b/packages/x-charts/src/internals/getBarDimensions.ts @@ -53,14 +53,10 @@ export function getBarDimensions(params: { return null; } - const visibleValues = series.visibleStackedData[dataIndex]; - const visibleValueCoordinates = visibleValues.map((v) => - verticalLayout ? yScale(v)! : xScale(v)!, - ); + const values = series.visibleStackedData[dataIndex]; + const valueCoordinates = values.map((v) => (verticalLayout ? yScale(v)! : xScale(v)!)); - const [minValueCoord, maxValueCoord] = findMinMax(visibleValueCoordinates).map((v) => - Math.round(v), - ); + const [minValueCoord, maxValueCoord] = findMinMax(valueCoordinates).map((v) => Math.round(v)); let barSize = 0; if (seriesValue !== 0) { From 49963d2b5c740ac763555c2e438a509aceae0943 Mon Sep 17 00:00:00 2001 From: Jose Quintas Date: Mon, 8 Dec 2025 12:21:35 +0100 Subject: [PATCH 8/9] fix getter --- .../corePlugins/useChartSeries/useChartSeries.selectors.ts | 2 +- .../useChartVisibilityManager.selectors.ts | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/x-charts/src/internals/plugins/corePlugins/useChartSeries/useChartSeries.selectors.ts b/packages/x-charts/src/internals/plugins/corePlugins/useChartSeries/useChartSeries.selectors.ts index 6499eb7966734..e2d84b694b90a 100644 --- a/packages/x-charts/src/internals/plugins/corePlugins/useChartSeries/useChartSeries.selectors.ts +++ b/packages/x-charts/src/internals/plugins/corePlugins/useChartSeries/useChartSeries.selectors.ts @@ -43,7 +43,7 @@ export const selectorChartSeriesProcessed = createSelectorMemoized( dataset, isIdentifierVisible, ) { - return applySeriesProcessors(defaultizedSeries, seriesConfig, dataset, isIdentifierVisible.get); + return applySeriesProcessors(defaultizedSeries, seriesConfig, dataset, isIdentifierVisible); }, ); diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.selectors.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.selectors.ts index 4aa5953c02871..56108f5a09b87 100644 --- a/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.selectors.ts +++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.selectors.ts @@ -35,10 +35,7 @@ export const selectorIsIdentifierVisible = createSelector( export const selectorIsIdentifierVisibleGetter = createSelectorMemoized( selectorVisibilityMap, (visibilityMap) => { - return { - // Return an object as selectors don't correctly memoize direct functions - get: (identifier: string | (string | number)[]) => - isIdentifierVisible(visibilityMap, identifier), - }; + return (identifier: string | (string | number)[]) => + isIdentifierVisible(visibilityMap, identifier); }, ); From 54ca8885063578dc13fc151c88be0fb35dd79780 Mon Sep 17 00:00:00 2001 From: Jose Quintas Date: Mon, 8 Dec 2025 12:53:01 +0100 Subject: [PATCH 9/9] typing --- .../PieChart/seriesConfig/seriesProcessor.ts | 4 +- .../isIdentifierVisible.ts | 17 +++--- .../useChartVisibilityManager.selectors.ts | 13 +++-- .../useChartVisibilityManager.ts | 23 ++++---- .../useChartVisibilityManager.types.ts | 55 +++++++------------ 5 files changed, 49 insertions(+), 63 deletions(-) diff --git a/packages/x-charts/src/PieChart/seriesConfig/seriesProcessor.ts b/packages/x-charts/src/PieChart/seriesConfig/seriesProcessor.ts index d8fed358dbb1e..9f1728a71e731 100644 --- a/packages/x-charts/src/PieChart/seriesConfig/seriesProcessor.ts +++ b/packages/x-charts/src/PieChart/seriesConfig/seriesProcessor.ts @@ -30,7 +30,7 @@ const seriesProcessor: SeriesProcessor<'pie'> = (params, dataset, isIdentifierVi // Filter out hidden data points for arc calculation const visibleData = series[seriesId].data.filter((piePoint, index) => { const itemId = piePoint.id ?? `auto-generated-pie-id-${seriesId}-${index}`; - return isIdentifierVisible?.([seriesId, itemId]); + return isIdentifierVisible?.(seriesId, itemId); }); const visibleArcs = d3Pie() @@ -49,7 +49,7 @@ const seriesProcessor: SeriesProcessor<'pie'> = (params, dataset, isIdentifierVi ...series[seriesId], data: series[seriesId].data.map((item, index) => { const itemId = item.id ?? `auto-generated-pie-id-${seriesId}-${index}`; - const isHidden = !isIdentifierVisible?.([seriesId, itemId]); + const isHidden = !isIdentifierVisible?.(seriesId, itemId); let arcData; if (isHidden) { diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/isIdentifierVisible.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/isIdentifierVisible.ts index 02d374fea6e18..ec669736baa5f 100644 --- a/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/isIdentifierVisible.ts +++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/isIdentifierVisible.ts @@ -1,23 +1,22 @@ -import type { VisibilityMap } from './useChartVisibilityManager.types'; +import type { VisibilityIdentifier, VisibilityMap } from './useChartVisibilityManager.types'; export const VISIBILITY_SEPARATOR = '-'; -export const buildIdentifier = (ids: string | (string | number)[]) => { - if (typeof ids === 'string') { - return ids; +export const buildIdentifier = (ids: VisibilityIdentifier[]): string => { + if (ids.length === 1 && (typeof ids[0] === 'string' || typeof ids[0] === 'number')) { + return String(ids[0]); } + return ids.filter((v) => v !== undefined && v !== null).join(VISIBILITY_SEPARATOR); }; export const isIdentifierVisible = ( visibilityMap: VisibilityMap, - identifier: string | (string | number)[], + identifiers: VisibilityIdentifier[], ) => { - if (Array.isArray(identifier)) { - identifier = buildIdentifier(identifier); - } + const id = buildIdentifier(identifiers); - const state = visibilityMap?.[identifier]; + const state = visibilityMap?.[id]; return state !== false; }; diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.selectors.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.selectors.ts index 56108f5a09b87..51987ee56a935 100644 --- a/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.selectors.ts +++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.selectors.ts @@ -1,5 +1,8 @@ import { createSelector, createSelectorMemoized } from '@mui/x-internals/store'; -import type { UseChartVisibilityManagerSignature } from './useChartVisibilityManager.types'; +import type { + UseChartVisibilityManagerSignature, + VisibilityIdentifier, +} from './useChartVisibilityManager.types'; import { type ChartOptionalRootSelector } from '../../utils/selectors'; import { isIdentifierVisible } from './isIdentifierVisible'; @@ -25,8 +28,8 @@ export const selectorVisibilityMap = createSelector( */ export const selectorIsIdentifierVisible = createSelector( selectorVisibilityMap, - (visibilityMap, identifier: string | (string | number)[]) => - isIdentifierVisible(visibilityMap, identifier), + (visibilityMap, identifiers: VisibilityIdentifier[]) => + isIdentifierVisible(visibilityMap, identifiers), ); /** @@ -35,7 +38,7 @@ export const selectorIsIdentifierVisible = createSelector( export const selectorIsIdentifierVisibleGetter = createSelectorMemoized( selectorVisibilityMap, (visibilityMap) => { - return (identifier: string | (string | number)[]) => - isIdentifierVisible(visibilityMap, identifier); + return (...identifiers: VisibilityIdentifier[]) => + isIdentifierVisible(visibilityMap, identifiers); }, ); diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.ts index b062837784322..9ad24a424cb93 100644 --- a/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.ts +++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.ts @@ -2,8 +2,11 @@ import useEventCallback from '@mui/utils/useEventCallback'; import { useEffectAfterFirstRender } from '@mui/x-internals/useEffectAfterFirstRender'; import { type ChartPlugin } from '../../models'; -import { type UseChartVisibilityManagerSignature } from './useChartVisibilityManager.types'; -import { buildIdentifier as buildIdentifierFn } from './isIdentifierVisible'; +import { + type UseChartVisibilityManagerSignature, + type VisibilityIdentifier, +} from './useChartVisibilityManager.types'; +import { buildIdentifier } from './isIdentifierVisible'; import { EMPTY_VISIBILITY_MAP } from './useChartVisibilityManager.selectors'; export const useChartVisibilityManager: ChartPlugin = ({ @@ -33,13 +36,9 @@ export const useChartVisibilityManager: ChartPlugin - buildIdentifierFn(ids), - ); - - const hideItem = useEventCallback((identifier: string | (number | string)[]) => { + const hideItem = useEventCallback((...identifiers: VisibilityIdentifier[]) => { const visibilityMap = store.state.visibilityManager.visibilityMap; - const id = buildIdentifier(identifier); + const id = buildIdentifier(identifiers); if (visibilityMap[id] === false) { return; // Already hidden @@ -54,9 +53,9 @@ export const useChartVisibilityManager: ChartPlugin { + const showItem = useEventCallback((...identifiers: VisibilityIdentifier[]) => { const visibilityMap = store.state.visibilityManager.visibilityMap; - const id = buildIdentifier(identifier); + const id = buildIdentifier(identifiers); if (visibilityMap[id] !== false) { return; // Already visible @@ -71,9 +70,9 @@ export const useChartVisibilityManager: ChartPlugin { + const toggleItem = useEventCallback((...identifiers: VisibilityIdentifier[]) => { const visibilityMap = store.state.visibilityManager.visibilityMap; - const id = buildIdentifier(identifier); + const id = buildIdentifier(identifiers); if (visibilityMap[id] === false) { showItem(id); diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.types.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.types.ts index 553c18da8b776..40bd77c3441ac 100644 --- a/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.types.ts +++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartVisibilityManager/useChartVisibilityManager.types.ts @@ -5,64 +5,49 @@ export type VisibilityMap = { [key: string]: boolean; }; +export type VisibilityIdentifier = string | number; + export type IsIdentifierVisibleFunction = { /** * Function to check if an item is visible based on its identifier. - * @param {string | (string | number)[]} identifier The identifier of the item. - * @returns {boolean} Whether the item is visible. - */ - (identifier: string): boolean; - /** - * Function overload to check if an item is visible based on its identifier array. - * @param {(string | number)[]} identifierArray The identifier array of the item. + * + * If more than one parameter is provided, they will be joined using '-' to form the identifier. + * Number values will be converted to strings. + * + * @param {VisibilityIdentifier} identifier The identifier of the item to check. * @returns {boolean} Whether the item is visible. */ - (identifierArray: (string | number)[]): boolean; + (...identifiers: VisibilityIdentifier[]): boolean; }; export interface UseChartVisibilityManagerPublicAPI { /** * Hide an item by its identifier. * - * @param {string} identifier The identifier of the item to hide. - */ - hideItem(identifier: string): void; - /** - * Hide an item by providing an array of identifiers to join. + * If more than one parameter is provided, they will be joined using '-' to form the identifier. + * Number values will be converted to strings. * - * The array will be joined using '-'. - * - * @param {(number | string)[]} identifierArray The identifiers array of the item to hide. + * @param {VisibilityIdentifier} identifiers The identifiers of the item to hide. */ - hideItem(identifierArray: (number | string)[]): void; + hideItem(...identifiers: VisibilityIdentifier[]): void; /** * Show an item by its identifier. * - * @param {string} identifier The identifier of the item to show. - */ - showItem(identifier: string): void; - /** - * Show an item by providing an array of identifiers to join. - * - * The array will be joined using '-'. + * If more than one parameter is provided, they will be joined using '-' to form the identifier. + * Number values will be converted to strings. * - * @param {(number | string)[]} identifierArray The identifiers array of the item to show. + * @param {VisibilityIdentifier} identifiers The identifiers of the item to show. */ - showItem(identifierArray: (number | string)[]): void; + showItem(...identifiers: VisibilityIdentifier[]): void; /** * Toggle the visibility of an item by its identifier. * - * @param {string | (number | string)[]} identifier The identifier of the item to toggle. - */ - toggleItem(identifier: string): void; - /** - * Toggle the visibility of an item by providing an array of identifiers to join. - * - * The array will be joined using '-'. + * If more than one parameter is provided, they will be joined using '-' to form the identifier. + * Number values will be converted to strings. * - * @param { (number | string)[]} identifierArray the identifiers array of the item to toggle. + * @param {VisibilityIdentifier} identifiers The identifiers of the item to toggle. */ - toggleItem(identifierArray: (number | string)[]): void; + toggleItem(...identifiers: VisibilityIdentifier[]): void; } export interface UseChartVisibilityManagerInstance extends UseChartVisibilityManagerPublicAPI {}