@mui/x-data-grid-premium
Version:
The Premium plan edition of the MUI X Data Grid Components.
590 lines (575 loc) • 29.6 kB
JavaScript
'use client';
import _extends from "@babel/runtime/helpers/esm/extends";
import * as React from 'react';
import debounce from '@mui/utils/debounce';
import { gridColumnGroupsLookupSelector, gridColumnGroupsUnwrappedModelSelector, gridRowIdSelector, gridRowNodeSelector, gridRowTreeSelector } from '@mui/x-data-grid-pro';
import { useGridApiMethod, useGridEvent, gridColumnLookupSelector, runIf, gridPivotActiveSelector, useGridRegisterPipeProcessor, gridColumnFieldsSelector, gridFilteredSortedDepthRowEntriesSelector, GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD } from '@mui/x-data-grid-pro/internals';
import { getRowGroupingFieldFromGroupingCriteria } from "../rowGrouping/gridRowGroupingUtils.js";
import { gridChartsPanelOpenSelector, gridChartsDimensionsSelector, gridChartsValuesSelector, gridChartsIntegrationActiveChartIdSelector, gridChartableColumnsSelector, gridChartsIntegrationChartsLookupSelector } from "./gridChartsIntegrationSelectors.js";
import { useGridChartsIntegrationContext } from "../../utils/useGridChartIntegration.js";
import { isBlockedForSection } from "./utils.js";
import { gridRowGroupingSanitizedModelSelector } from "../rowGrouping/gridRowGroupingSelector.js";
import { GridSidebarValue } from "../sidebar/index.js";
import { getAggregationFunctionLabel, getAvailableAggregationFunctions } from "../aggregation/gridAggregationUtils.js";
import { gridAggregationModelSelector } from "../aggregation/gridAggregationSelectors.js";
import { gridPivotModelSelector } from "../pivoting/gridPivotingSelectors.js";
import { jsx as _jsx } from "react/jsx-runtime";
const EMPTY_CHART_INTEGRATION_CONTEXT = {
chartStateLookup: {},
setChartState: () => {}
};
export const EMPTY_CHART_INTEGRATION_CONTEXT_STATE = {
synced: true,
dimensions: [],
values: [],
type: '',
configuration: {}
};
export const chartsIntegrationStateInitializer = (state, props) => {
if (!props.chartsIntegration || !props.experimentalFeatures?.charts) {
return _extends({}, state, {
chartsIntegration: {
activeChartId: '',
charts: {}
}
});
}
const rowGroupingModel = (state.rowGrouping?.model ?? []).filter(item => item !== undefined);
const pivotModel = state.pivoting?.active ? state.pivoting?.model : undefined;
const columnsLookup = state.columns?.lookup ?? {};
const charts = Object.fromEntries(Object.entries(props.initialState?.chartsIntegration?.charts || {}).map(([chartId, chart]) => [chartId, {
dimensions: (chart.dimensions || []).map(dimension => typeof dimension === 'string' ? {
field: dimension,
hidden: false
} : dimension).filter(dimension => columnsLookup[dimension.field]?.chartable === true && !isBlockedForSection(columnsLookup[dimension.field], 'dimensions', rowGroupingModel, pivotModel)),
values: (chart.values || []).map(value => typeof value === 'string' ? {
field: value,
hidden: false
} : value).filter(value => columnsLookup[value.field]?.chartable === true && !isBlockedForSection(columnsLookup[value.field], 'values', rowGroupingModel, pivotModel))
}]));
return _extends({}, state, {
chartsIntegration: {
activeChartId: props.activeChartId ?? props.initialState?.chartsIntegration?.activeChartId ?? '',
charts
}
});
};
export const useGridChartsIntegration = (apiRef, props) => {
const visibleDimensions = React.useRef({});
const visibleValues = React.useRef({});
const schema = React.useMemo(() => props.slotProps?.chartsPanel?.schema || {}, [props.slotProps?.chartsPanel?.schema]);
const context = useGridChartsIntegrationContext(true);
const isChartsIntegrationAvailable = !!props.chartsIntegration && !!props.experimentalFeatures?.charts && !!context;
const activeChartId = gridChartsIntegrationActiveChartIdSelector(apiRef);
const aggregationModel = gridAggregationModelSelector(apiRef);
const pivotActive = gridPivotActiveSelector(apiRef);
const pivotModel = gridPivotModelSelector(apiRef);
const {
chartStateLookup,
setChartState
} = context || EMPTY_CHART_INTEGRATION_CONTEXT;
const availableChartIds = React.useMemo(() => {
const ids = Object.keys(chartStateLookup);
// cleanup visibleDimensions and visibleValues references
Object.keys(visibleDimensions.current).forEach(chartId => {
if (!ids.includes(chartId)) {
delete visibleDimensions.current[chartId];
delete visibleValues.current[chartId];
}
});
return ids;
}, [chartStateLookup]);
const syncedChartIds = React.useMemo(() => availableChartIds.filter(chartId => chartStateLookup[chartId].synced !== false), [availableChartIds, chartStateLookup]);
const getColumnName = React.useCallback(field => {
const customFieldName = props.slotProps?.chartsPanel?.getColumnName?.(field);
if (customFieldName) {
return customFieldName;
}
const columns = gridColumnLookupSelector(apiRef);
const columnGroupPath = gridColumnGroupsUnwrappedModelSelector(apiRef)[field] ?? [];
const columnGroupLookup = gridColumnGroupsLookupSelector(apiRef);
const column = columns[field];
const columnName = column?.headerName || field;
if (!pivotActive || !columnGroupPath) {
return columnName;
}
const groupNames = columnGroupPath.map(group => columnGroupLookup[group].headerName || group);
return [columnName, ...groupNames].join(' - ');
}, [apiRef, pivotActive, props.slotProps?.chartsPanel]);
// Adds aggregation function label to the column name
const getValueDatasetLabel = React.useCallback(field => {
const customFieldName = props.slotProps?.chartsPanel?.getColumnName?.(field);
if (customFieldName) {
return customFieldName;
}
const columnName = getColumnName(field);
const fieldAggregation = gridAggregationModelSelector(apiRef)[field];
const suffix = fieldAggregation ? ` (${getAggregationFunctionLabel({
apiRef,
aggregationRule: {
aggregationFunctionName: fieldAggregation,
aggregationFunction: props.aggregationFunctions[fieldAggregation] || {}
}
})})` : '';
return `${columnName}${suffix}`;
}, [apiRef, props.aggregationFunctions, props.slotProps?.chartsPanel, getColumnName]);
apiRef.current.registerControlState({
stateId: 'activeChartId',
propModel: props.activeChartId,
propOnChange: props.onActiveChartIdChange,
stateSelector: gridChartsIntegrationActiveChartIdSelector,
changeEvent: 'activeChartIdChange'
});
// sometimes, updates made to the chart dimensions and values require updating other models
// for example, if we are adding more than one dimension, we need to set the new grouping model
// if we are adding new value dataset to the grouped data, we need to set the aggregation model, otherwise the values will be undefined
const updateOtherModels = React.useCallback(() => {
const rowGroupingModel = gridRowGroupingSanitizedModelSelector(apiRef);
if (visibleDimensions.current[activeChartId]?.length > 0 && (
// if there was row grouping or if we are adding more than one dimension, set the new grouping model
rowGroupingModel.length > 0 || visibleDimensions.current[activeChartId].length > 1) &&
// if row grouping model starts with dimensions in the same order, we don't have to do anything
visibleDimensions.current[activeChartId].some((item, index) => item.field !== rowGroupingModel[index])) {
// if pivoting is enabled, then the row grouping model is driven by the pivoting rows
const newGroupingModel = visibleDimensions.current[activeChartId].map(item => item.field);
if (pivotActive) {
apiRef.current.setPivotModel(prev => _extends({}, prev, {
rows: newGroupingModel.map(item => ({
field: item,
hidden: false
}))
}));
} else {
apiRef.current.setRowGroupingModel(newGroupingModel);
apiRef.current.setColumnVisibilityModel(_extends({}, apiRef.current.state.columns.columnVisibilityModel, Object.fromEntries(rowGroupingModel.map(item => [item, true])), Object.fromEntries(newGroupingModel.map(item => [item, false]))));
}
}
if (!pivotActive && visibleValues.current[activeChartId] && rowGroupingModel.length > 0) {
// with row grouping add the aggregation model to the newly added value dataset
const aggregatedFields = Object.keys(aggregationModel);
const aggregationsToAdd = {};
visibleValues.current[activeChartId].forEach(item => {
const hasAggregation = aggregatedFields.includes(item.field);
if (!hasAggregation) {
// use the first available aggregation function
aggregationsToAdd[item.field] = getAvailableAggregationFunctions({
aggregationFunctions: props.aggregationFunctions,
colDef: item,
isDataSource: !!props.dataSource
})[0];
}
});
if (Object.keys(aggregationsToAdd).length > 0) {
apiRef.current.setAggregationModel(_extends({}, aggregationModel, aggregationsToAdd));
}
}
}, [apiRef, props.aggregationFunctions, props.dataSource, activeChartId, pivotActive, aggregationModel]);
const handleRowDataUpdate = React.useCallback(chartIds => {
if (chartIds.length === 0 || chartIds.some(chartId => !visibleDimensions.current[chartId] || !visibleValues.current[chartId])) {
return;
}
const orderedFields = gridColumnFieldsSelector(apiRef);
const rowGroupingModel = gridRowGroupingSanitizedModelSelector(apiRef);
const rowTree = gridRowTreeSelector(apiRef);
const rowsPerDepth = gridFilteredSortedDepthRowEntriesSelector(apiRef);
const currentChartId = gridChartsIntegrationActiveChartIdSelector(apiRef);
const defaultDepth = Math.max(0, (visibleDimensions.current[currentChartId]?.length ?? 0) - 1);
const rowsAtDefaultDepth = (rowsPerDepth[defaultDepth] ?? []).length;
// keep only unique columns and transform the grouped column to carry the correct field name to get the grouped value
const dataColumns = [...new Set([...Object.values(visibleDimensions.current).flat(), ...Object.values(visibleValues.current).flat()])].map(column => {
const isColumnGrouped = rowGroupingModel.includes(column.field);
if (isColumnGrouped) {
const groupedFieldName = isColumnGrouped ? getRowGroupingFieldFromGroupingCriteria(orderedFields.includes(GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD) ? null : column.field) : column.field;
const columnDefinition = apiRef.current.getColumn(groupedFieldName);
return _extends({}, columnDefinition, {
dataFieldName: column.field,
depth: rowGroupingModel.indexOf(column.field)
});
}
return _extends({}, column, {
dataFieldName: column.field,
depth: defaultDepth
});
});
// go through the data only once and collect everything that will be needed
const data = Object.fromEntries(dataColumns.map(column => [column.dataFieldName, []]));
const dataColumnsCount = dataColumns.length;
for (let i = 0; i < rowsAtDefaultDepth; i += 1) {
for (let j = 0; j < dataColumnsCount; j += 1) {
// if multiple columns are grouped, we need to get the value from the parent to properly create dimension groups
let targetRow = rowsPerDepth[defaultDepth][i].model;
// if we are not at the same depth as the column we are currently processing change the target to the parent at the correct depth
for (let d = defaultDepth; d > dataColumns[j].depth; d -= 1) {
const rowId = gridRowIdSelector(apiRef, targetRow);
targetRow = gridRowNodeSelector(apiRef, rowTree[rowId].parent);
}
const value = apiRef.current.getRowValue(targetRow, dataColumns[j]);
if (value !== null) {
data[dataColumns[j].dataFieldName].push(typeof value === 'object' && 'label' in value ? value.label : value);
}
}
}
chartIds.forEach(chartId => {
setChartState(chartId, {
dimensions: visibleDimensions.current[chartId].map(dimension => ({
id: dimension.field,
label: getColumnName(dimension.field),
data: data[dimension.field] || []
})),
values: visibleValues.current[chartId].map(value => ({
id: value.field,
label: getValueDatasetLabel(value.field),
data: data[value.field] || []
}))
});
});
}, [apiRef, getColumnName, getValueDatasetLabel, setChartState]);
const debouncedHandleRowDataUpdate = React.useMemo(() => debounce(handleRowDataUpdate, 0), [handleRowDataUpdate]);
const handleColumnDataUpdate = React.useCallback((chartIds, updatedChartStateLookup) => {
// if there are no charts, skip the data processing
if (chartIds.length === 0) {
return;
}
const rowGroupingModel = gridRowGroupingSanitizedModelSelector(apiRef);
const chartableColumns = gridChartableColumnsSelector(apiRef);
const selectedFields = chartIds.reduce((acc, chartId) => {
const values = gridChartsValuesSelector(apiRef, chartId);
const dimensions = gridChartsDimensionsSelector(apiRef, chartId);
return _extends({}, acc, {
[chartId]: {
values,
dimensions
}
});
}, {});
const values = {};
const dimensions = {};
chartIds.forEach(chartId => {
dimensions[chartId] = [];
values[chartId] = [];
// loop through dimensions and values datasets either through their length or to the max limit
// if the current selection is greater than the max limit, the state will be updated
const chartState = updatedChartStateLookup?.[chartId] || chartStateLookup[chartId];
const dimensionsSize = chartState?.maxDimensions ? Math.min(chartState.maxDimensions, selectedFields[chartId].dimensions.length) : selectedFields[chartId].dimensions.length;
const valuesSize = chartState?.maxValues ? Math.min(chartState.maxValues, selectedFields[chartId].values.length) : selectedFields[chartId].values.length;
// sanitize selectedDimensions and selectedValues while maintaining their order
for (let i = 0; i < valuesSize; i += 1) {
if (chartableColumns[selectedFields[chartId].values[i].field] && !isBlockedForSection(chartableColumns[selectedFields[chartId].values[i].field], 'values', rowGroupingModel, pivotActive ? pivotModel : undefined)) {
if (!values[chartId]) {
values[chartId] = [];
}
values[chartId].push(selectedFields[chartId].values[i]);
}
}
// dimensions cannot contain fields that are already in values
for (let i = 0; i < dimensionsSize; i += 1) {
const item = selectedFields[chartId].dimensions[i];
if (!selectedFields[chartId].values.some(valueItem => valueItem.field === item.field) && chartableColumns[item.field] && !isBlockedForSection(chartableColumns[item.field], 'dimensions', rowGroupingModel, pivotActive ? pivotModel : undefined)) {
if (!dimensions[chartId]) {
dimensions[chartId] = [];
}
dimensions[chartId].push(item);
}
}
// we can compare the lengths, because this function is called after the state was updated.
// different lengths will occur only if some items were removed during the checks above
if (dimensions[chartId] && selectedFields[chartId].dimensions.length !== dimensions[chartId].length) {
apiRef.current.updateChartDimensionsData(chartId, dimensions[chartId]);
}
if (values[chartId] && selectedFields[chartId].values.length !== values[chartId].length) {
apiRef.current.updateChartValuesData(chartId, values[chartId]);
}
visibleDimensions.current[chartId] = dimensions[chartId].filter(dimension => dimension.hidden !== true).map(dimension => chartableColumns[dimension.field]);
visibleValues.current[chartId] = values[chartId].filter(value => value.hidden !== true).map(value => chartableColumns[value.field]);
// we need to have both dimensions and values to be able to display the chart
if (visibleDimensions.current[chartId].length === 0 || visibleValues.current[chartId].length === 0) {
visibleDimensions.current[chartId] = [];
visibleValues.current[chartId] = [];
}
});
updateOtherModels();
debouncedHandleRowDataUpdate(chartIds);
}, [apiRef, chartStateLookup, pivotActive, pivotModel, debouncedHandleRowDataUpdate, updateOtherModels]);
const debouncedHandleColumnDataUpdate = React.useMemo(() => debounce(handleColumnDataUpdate, 0), [handleColumnDataUpdate]);
const setChartsPanelOpen = React.useCallback(callback => {
if (!isChartsIntegrationAvailable) {
return;
}
const panelOpen = gridChartsPanelOpenSelector(apiRef);
const newPanelOpen = typeof callback === 'function' ? callback(panelOpen) : callback;
if (panelOpen === newPanelOpen) {
return;
}
if (newPanelOpen) {
apiRef.current.showSidebar(GridSidebarValue.Charts);
} else {
apiRef.current.hideSidebar();
}
}, [apiRef, isChartsIntegrationAvailable]);
const updateChartDimensionsData = React.useCallback((chartId, dimensions) => {
if (!isChartsIntegrationAvailable) {
return;
}
apiRef.current.setState(state => {
const newDimensions = typeof dimensions === 'function' ? dimensions(state.chartsIntegration.charts[chartId].dimensions) : dimensions;
return _extends({}, state, {
chartsIntegration: _extends({}, state.chartsIntegration, {
charts: _extends({}, state.chartsIntegration.charts, {
[chartId]: _extends({}, state.chartsIntegration.charts[chartId], {
dimensions: newDimensions
})
})
})
});
});
debouncedHandleColumnDataUpdate(syncedChartIds);
}, [apiRef, isChartsIntegrationAvailable, syncedChartIds, debouncedHandleColumnDataUpdate]);
const updateChartValuesData = React.useCallback((chartId, values) => {
if (!isChartsIntegrationAvailable) {
return;
}
apiRef.current.setState(state => {
const newValues = typeof values === 'function' ? values(state.chartsIntegration.charts[chartId].values) : values;
return _extends({}, state, {
chartsIntegration: _extends({}, state.chartsIntegration, {
charts: _extends({}, state.chartsIntegration.charts, {
[chartId]: _extends({}, state.chartsIntegration.charts[chartId], {
values: newValues
})
})
})
});
});
debouncedHandleColumnDataUpdate(syncedChartIds);
}, [apiRef, isChartsIntegrationAvailable, syncedChartIds, debouncedHandleColumnDataUpdate]);
const setActiveChartId = React.useCallback(chartId => {
if (!isChartsIntegrationAvailable) {
return;
}
apiRef.current.setState(state => _extends({}, state, {
chartsIntegration: _extends({}, state.chartsIntegration, {
activeChartId: chartId
})
}));
}, [apiRef, isChartsIntegrationAvailable]);
const setChartType = React.useCallback((chartId, type) => {
if (!isChartsIntegrationAvailable || !chartStateLookup[chartId]) {
return;
}
const stateUpdate = {
type,
dimensionsLabel: schema[type]?.dimensionsLabel,
valuesLabel: schema[type]?.valuesLabel,
maxDimensions: schema[type]?.maxDimensions,
maxValues: schema[type]?.maxValues
};
const updatedChartStateLookup = _extends({}, chartStateLookup, {
[chartId]: _extends({}, chartStateLookup[chartId], stateUpdate)
});
// make sure that the new dimensions and values limits are applied before changing the chart type
handleColumnDataUpdate([chartId], updatedChartStateLookup);
setChartState(chartId, stateUpdate);
}, [isChartsIntegrationAvailable, chartStateLookup, schema, setChartState, handleColumnDataUpdate]);
const setChartSynchronizationState = React.useCallback((chartId, synced) => {
if (!isChartsIntegrationAvailable || !chartStateLookup[chartId]) {
return;
}
const stateUpdate = {
synced
};
const updatedChartStateLookup = _extends({}, chartStateLookup, {
[chartId]: _extends({}, chartStateLookup[chartId], stateUpdate)
});
setChartState(chartId, stateUpdate);
apiRef.current.publishEvent('chartSynchronizationStateChange', {
chartId,
synced
});
if (synced) {
debouncedHandleColumnDataUpdate([chartId], updatedChartStateLookup);
}
}, [apiRef, isChartsIntegrationAvailable, chartStateLookup, setChartState, debouncedHandleColumnDataUpdate]);
// called when a column is dragged and dropped to a different section
const updateDataReference = React.useCallback((field, originSection, targetSection, targetField, placementRelativeToTargetField) => {
const columns = gridColumnLookupSelector(apiRef);
const dimensions = gridChartsDimensionsSelector(apiRef, activeChartId);
const values = gridChartsValuesSelector(apiRef, activeChartId);
const rowGroupingModel = gridRowGroupingSanitizedModelSelector(apiRef);
if (targetSection) {
if (isBlockedForSection(columns[field], targetSection, rowGroupingModel, pivotActive ? pivotModel : undefined)) {
return;
}
const currentTargetItems = targetSection === 'dimensions' ? dimensions : values;
const currentMaxItems = targetSection === 'dimensions' ? chartStateLookup[activeChartId]?.maxDimensions : chartStateLookup[activeChartId]?.maxValues;
if (currentMaxItems && currentTargetItems.length >= currentMaxItems) {
return;
}
}
let hidden;
if (originSection) {
const method = originSection === 'dimensions' ? updateChartDimensionsData : updateChartValuesData;
const currentItems = originSection === 'dimensions' ? [...dimensions] : [...values];
const fieldIndex = currentItems.findIndex(item => item.field === field);
if (fieldIndex !== -1) {
hidden = currentItems[fieldIndex].hidden;
}
// if the target is another section, remove the field from the origin section
if (targetSection !== originSection) {
currentItems.splice(fieldIndex, 1);
method(activeChartId, currentItems);
}
}
if (targetSection) {
const method = targetSection === 'dimensions' ? updateChartDimensionsData : updateChartValuesData;
const currentItems = targetSection === 'dimensions' ? dimensions : values;
const remainingItems = targetSection === originSection ? currentItems.filter(item => item.field !== field) : [...currentItems];
// with row grouping add the aggregation model to the newly added values dataset
if (rowGroupingModel.length > 0 && targetSection === 'values') {
const hasAggregation = Object.keys(aggregationModel).includes(field);
if (!hasAggregation) {
apiRef.current.setAggregationModel(_extends({}, aggregationModel, {
[field]: getAvailableAggregationFunctions({
aggregationFunctions: props.aggregationFunctions,
colDef: columns[field],
isDataSource: !!props.dataSource
})[0]
}));
}
}
if (targetField) {
const targetFieldIndex = remainingItems.findIndex(item => item.field === targetField);
const targetIndex = placementRelativeToTargetField === 'top' ? targetFieldIndex : targetFieldIndex + 1;
remainingItems.splice(targetIndex, 0, {
field,
hidden
});
method(activeChartId, remainingItems);
} else {
method(activeChartId, [...remainingItems, {
field,
hidden
}]);
}
}
}, [apiRef, props.aggregationFunctions, props.dataSource, activeChartId, chartStateLookup, updateChartDimensionsData, updateChartValuesData, aggregationModel, pivotActive, pivotModel]);
const addColumnMenuButton = React.useCallback(menuItems => {
if (isChartsIntegrationAvailable) {
return [...menuItems, 'columnMenuManagePanelItem'];
}
return menuItems;
}, [isChartsIntegrationAvailable]);
useGridRegisterPipeProcessor(apiRef, 'columnMenu', addColumnMenuButton);
const addChartsPanel = React.useCallback((initialValue, value) => {
if (props.slots.chartsPanel && isChartsIntegrationAvailable && value === GridSidebarValue.Charts) {
return /*#__PURE__*/_jsx(props.slots.chartsPanel, _extends({}, props.slotProps?.chartsPanel));
}
return initialValue;
}, [props, isChartsIntegrationAvailable]);
useGridRegisterPipeProcessor(apiRef, 'sidebar', addChartsPanel);
useGridApiMethod(apiRef, {
chartsIntegration: {
updateDataReference,
getColumnName
}
}, 'private');
useGridApiMethod(apiRef, props.experimentalFeatures?.charts ? {
setChartsPanelOpen,
setActiveChartId,
setChartType,
setChartSynchronizationState,
updateChartDimensionsData,
updateChartValuesData
} : {}, 'public');
useGridEvent(apiRef, 'columnsChange', runIf(isChartsIntegrationAvailable, () => debouncedHandleColumnDataUpdate(syncedChartIds)));
useGridEvent(apiRef, 'pivotModeChange', runIf(isChartsIntegrationAvailable, () => debouncedHandleColumnDataUpdate(syncedChartIds)));
useGridEvent(apiRef, 'filteredRowsSet', runIf(isChartsIntegrationAvailable, () => debouncedHandleRowDataUpdate(syncedChartIds)));
useGridEvent(apiRef, 'sortedRowsSet', runIf(isChartsIntegrationAvailable, () => debouncedHandleRowDataUpdate(syncedChartIds)));
useGridEvent(apiRef, 'aggregationLookupSet', runIf(isChartsIntegrationAvailable, () => debouncedHandleRowDataUpdate(syncedChartIds)));
const stateExportPreProcessing = React.useCallback((prevState, exportContext) => {
if (!props.chartsIntegration || !props.experimentalFeatures?.charts) {
return prevState;
}
const currentActiveChartId = gridChartsIntegrationActiveChartIdSelector(apiRef);
const chartsLookup = gridChartsIntegrationChartsLookupSelector(apiRef);
const integrationContextToExport = Object.fromEntries(Object.entries(chartStateLookup).map(([chartId, chartState]) => [chartId,
// keep only the state that is controlled by the user, drop the data and labels
{
synced: chartState.synced,
type: chartState.type,
configuration: chartState.configuration
}]));
const shouldExportChartState =
// Always export if the `exportOnlyDirtyModels` property is not activated
!exportContext.exportOnlyDirtyModels ||
// Always export if the chart state has been initialized
props.initialState?.chartsIntegration != null ||
// Export if the chart model or context is not empty
Object.keys(chartsLookup).length > 0 || Object.keys(integrationContextToExport).length > 0;
if (!shouldExportChartState) {
return prevState;
}
const chartStateToExport = {
activeChartId: currentActiveChartId,
charts: chartsLookup,
// add a custom prop to keep the integration context in the exported state
integrationContext: integrationContextToExport
};
return _extends({}, prevState, {
chartsIntegration: chartStateToExport
});
}, [apiRef, chartStateLookup, props.chartsIntegration, props.experimentalFeatures?.charts, props.initialState?.chartsIntegration]);
const stateRestorePreProcessing = React.useCallback((params, restoreContext) => {
const chartsRestoreState = restoreContext.stateToRestore.chartsIntegration;
if (!chartsRestoreState) {
return params;
}
const {
activeChartId: activeChartIdToRestore,
charts: chartsToRestore,
integrationContext
} = chartsRestoreState;
if (activeChartIdToRestore === undefined || chartsToRestore === undefined || Object.keys(chartsToRestore).length === 0) {
return params;
}
apiRef.current.setState(_extends({}, apiRef.current.state, {
chartsIntegration: {
activeChartId: activeChartIdToRestore,
charts: chartsToRestore
}
}));
// restore the integration context for each chart
Object.entries(integrationContext).forEach(([chartId, chartContextState]) => {
setChartState(chartId, chartContextState);
});
return params;
}, [apiRef, setChartState]);
useGridRegisterPipeProcessor(apiRef, 'exportState', stateExportPreProcessing);
useGridRegisterPipeProcessor(apiRef, 'restoreState', stateRestorePreProcessing);
React.useEffect(() => {
if (!activeChartId && availableChartIds.length > 0) {
setActiveChartId(availableChartIds[0]);
}
}, [availableChartIds, activeChartId, setActiveChartId]);
const isInitialized = React.useRef(false);
React.useEffect(() => {
if (isInitialized.current) {
return;
}
if (availableChartIds.length === 0) {
return;
}
isInitialized.current = true;
availableChartIds.forEach(chartId => {
const chartType = props.initialState?.chartsIntegration?.charts?.[chartId]?.chartType || '';
setChartState(chartId, {
type: chartType,
maxDimensions: schema[chartType]?.maxDimensions,
maxValues: schema[chartType]?.maxValues,
dimensionsLabel: schema[chartType]?.dimensionsLabel,
valuesLabel: schema[chartType]?.valuesLabel,
configuration: props.initialState?.chartsIntegration?.charts?.[chartId]?.configuration || {}
});
});
debouncedHandleColumnDataUpdate(syncedChartIds);
}, [schema, availableChartIds, syncedChartIds, props.initialState?.chartsIntegration?.charts, setChartState, debouncedHandleColumnDataUpdate]);
};