UNPKG

@mui/x-charts

Version:

The community edition of MUI X Charts components.

222 lines (217 loc) 9.09 kB
'use client'; import _extends from "@babel/runtime/helpers/esm/extends"; import * as React from 'react'; import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; import { useAssertModelConsistency } from '@mui/x-internals/useAssertModelConsistency'; import { warnOnce } from '@mui/x-internals/warning'; import { rainbowSurgePalette } from "../../../../colorPalettes/index.js"; import { useSelector } from "../../../store/useSelector.js"; import { selectorChartDrawingArea } from "../../corePlugins/useChartDimensions/useChartDimensions.selectors.js"; import { selectorChartSeriesProcessed } from "../../corePlugins/useChartSeries/useChartSeries.selectors.js"; import { defaultizeXAxis, defaultizeYAxis } from "./defaultizeAxis.js"; import { selectorChartXAxis, selectorChartYAxis } from "./useChartCartesianAxisRendering.selectors.js"; import { getAxisIndex } from "./getAxisValue.js"; import { getSVGPoint } from "../../../getSVGPoint.js"; import { selectorChartsInteractionIsInitialized } from "../useChartInteraction/index.js"; import { selectorChartAxisInteraction } from "./useChartCartesianInteraction.selectors.js"; import { useLazySelectorEffect } from "../../utils/useLazySelectorEffect.js"; export const useChartCartesianAxis = ({ params, store, seriesConfig, svgRef, instance }) => { const { xAxis, yAxis, dataset, onHighlightedAxisChange } = params; if (process.env.NODE_ENV !== 'production') { const ids = [...(xAxis ?? []), ...(yAxis ?? [])].filter(axis => axis.id).map(axis => axis.id); const duplicates = new Set(ids.filter((id, index) => ids.indexOf(id) !== index)); if (duplicates.size > 0) { warnOnce([`MUI X Charts: The following axis ids are duplicated: ${Array.from(duplicates).join(', ')}.`, `Please make sure that each axis has a unique id.`].join('\n'), 'error'); } } const drawingArea = useSelector(store, selectorChartDrawingArea); const processedSeries = useSelector(store, selectorChartSeriesProcessed); const isInteractionEnabled = useSelector(store, selectorChartsInteractionIsInitialized); const { axis: xAxisWithScale, axisIds: xAxisIds } = useSelector(store, selectorChartXAxis); const { axis: yAxisWithScale, axisIds: yAxisIds } = useSelector(store, selectorChartYAxis); useAssertModelConsistency({ warningPrefix: 'MUI X Charts', componentName: 'Chart', propName: 'highlightedAxis', controlled: params.highlightedAxis, defaultValue: undefined }); useEnhancedEffect(() => { if (params.highlightedAxis !== undefined) { store.update(prevState => { if (prevState.controlledCartesianAxisHighlight === params.highlightedAxis) { return prevState; } return _extends({}, prevState, { controlledCartesianAxisHighlight: params.highlightedAxis }); }); } }, [store, params.highlightedAxis]); // The effect do not track any value defined synchronously during the 1st render by hooks called after `useChartCartesianAxis` // As a consequence, the state generated by the 1st run of this useEffect will always be equal to the initialization one const isFirstRender = React.useRef(true); React.useEffect(() => { if (isFirstRender.current) { isFirstRender.current = false; return; } store.update(prev => _extends({}, prev, { cartesianAxis: _extends({}, prev.cartesianAxis, { x: defaultizeXAxis(xAxis, dataset), y: defaultizeYAxis(yAxis, dataset) }) })); }, [seriesConfig, drawingArea, xAxis, yAxis, dataset, store]); const usedXAxis = xAxisIds[0]; const usedYAxis = yAxisIds[0]; useLazySelectorEffect(store, selectorChartAxisInteraction, (prevAxisInteraction, nextAxisInteraction) => { if (Object.is(prevAxisInteraction, nextAxisInteraction)) { return; } if (prevAxisInteraction.length !== nextAxisInteraction.length) { onHighlightedAxisChange(nextAxisInteraction); return; } if (prevAxisInteraction?.some(({ axisId, dataIndex }, itemIndex) => nextAxisInteraction[itemIndex].axisId !== axisId || nextAxisInteraction[itemIndex].dataIndex !== dataIndex)) { onHighlightedAxisChange(nextAxisInteraction); } }, !onHighlightedAxisChange); React.useEffect(() => { const element = svgRef.current; if (!isInteractionEnabled || !element || params.disableAxisListener) { return () => {}; } // Clean the interaction when the mouse leaves the chart. const moveEndHandler = instance.addInteractionListener('moveEnd', event => { if (!event.detail.activeGestures.pan) { instance.cleanInteraction?.(); } }); const panEndHandler = instance.addInteractionListener('panEnd', event => { if (!event.detail.activeGestures.move) { instance.cleanInteraction?.(); } }); const pressEndHandler = instance.addInteractionListener('quickPressEnd', event => { if (!event.detail.activeGestures.move && !event.detail.activeGestures.pan) { instance.cleanInteraction?.(); } }); const gestureHandler = event => { const srvEvent = event.detail.srcEvent; const target = event.detail.target; const svgPoint = getSVGPoint(element, srvEvent); // Release the pointer capture if we are panning, as this would cause the tooltip to // be locked to the first "section" it touches. if (event.detail.srcEvent.buttons >= 1 && target?.hasPointerCapture(event.detail.srcEvent.pointerId) && !target?.closest('[data-charts-zoom-slider]')) { target?.releasePointerCapture(event.detail.srcEvent.pointerId); } if (!instance.isPointInside(svgPoint.x, svgPoint.y, target)) { instance.cleanInteraction?.(); return; } instance.setPointerCoordinate?.(svgPoint); }; const moveHandler = instance.addInteractionListener('move', gestureHandler); const panHandler = instance.addInteractionListener('pan', gestureHandler); const pressHandler = instance.addInteractionListener('quickPress', gestureHandler); return () => { moveHandler.cleanup(); moveEndHandler.cleanup(); panHandler.cleanup(); panEndHandler.cleanup(); pressHandler.cleanup(); pressEndHandler.cleanup(); }; }, [svgRef, store, xAxisWithScale, usedXAxis, yAxisWithScale, usedYAxis, instance, params.disableAxisListener, isInteractionEnabled]); React.useEffect(() => { const element = svgRef.current; const onAxisClick = params.onAxisClick; if (element === null || !onAxisClick) { return () => {}; } const axisClickHandler = instance.addInteractionListener('tap', event => { let dataIndex = null; let isXAxis = false; const svgPoint = getSVGPoint(element, event.detail.srcEvent); const xIndex = getAxisIndex(xAxisWithScale[usedXAxis], svgPoint.x); isXAxis = xIndex !== -1; dataIndex = isXAxis ? xIndex : getAxisIndex(yAxisWithScale[usedYAxis], svgPoint.y); const USED_AXIS_ID = isXAxis ? xAxisIds[0] : yAxisIds[0]; if (dataIndex == null || dataIndex === -1) { return; } // The .data exist because otherwise the dataIndex would be null or -1. const axisValue = (isXAxis ? xAxisWithScale : yAxisWithScale)[USED_AXIS_ID].data[dataIndex]; const seriesValues = {}; Object.keys(processedSeries).filter(seriesType => ['bar', 'line'].includes(seriesType)).forEach(seriesType => { processedSeries[seriesType]?.seriesOrder.forEach(seriesId => { const seriesItem = processedSeries[seriesType].series[seriesId]; const providedXAxisId = seriesItem.xAxisId; const providedYAxisId = seriesItem.yAxisId; const axisKey = isXAxis ? providedXAxisId : providedYAxisId; if (axisKey === undefined || axisKey === USED_AXIS_ID) { seriesValues[seriesId] = seriesItem.data[dataIndex]; } }); }); onAxisClick(event.detail.srcEvent, { dataIndex, axisValue, seriesValues }); }); return () => { axisClickHandler.cleanup(); }; }, [params.onAxisClick, processedSeries, svgRef, xAxisWithScale, xAxisIds, yAxisWithScale, yAxisIds, usedXAxis, usedYAxis, instance]); return {}; }; useChartCartesianAxis.params = { xAxis: true, yAxis: true, dataset: true, onAxisClick: true, disableAxisListener: true, onHighlightedAxisChange: true, highlightedAxis: true }; useChartCartesianAxis.getDefaultizedParams = ({ params }) => { return _extends({}, params, { colors: params.colors ?? rainbowSurgePalette, theme: params.theme ?? 'light', defaultizedXAxis: defaultizeXAxis(params.xAxis, params.dataset), defaultizedYAxis: defaultizeYAxis(params.yAxis, params.dataset) }); }; useChartCartesianAxis.getInitialState = params => _extends({ cartesianAxis: { x: params.defaultizedXAxis, y: params.defaultizedYAxis } }, params.highlightedAxis === undefined ? {} : { controlledCartesianAxisHighlight: params.highlightedAxis });