UNPKG

@mui/x-charts

Version:

The community edition of MUI X Charts components.

189 lines (187 loc) 6.71 kB
'use client'; import _extends from "@babel/runtime/helpers/esm/extends"; import * as React from 'react'; import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; import ownerWindow from '@mui/utils/ownerWindow'; import { useSelector } from "../../../store/useSelector.js"; import { DEFAULT_MARGINS } from "../../../../constants/index.js"; import { selectorChartDrawingArea } from "./useChartDimensions.selectors.js"; import { defaultizeMargin } from "../../../defaultizeMargin.js"; const MAX_COMPUTE_RUN = 10; export const useChartDimensions = ({ params, store, svgRef }) => { const hasInSize = params.width !== undefined && params.height !== undefined; const stateRef = React.useRef({ displayError: false, initialCompute: true, computeRun: 0 }); // States only used for the initialization of the size. const [innerWidth, setInnerWidth] = React.useState(0); const [innerHeight, setInnerHeight] = React.useState(0); const computeSize = React.useCallback(() => { const mainEl = svgRef?.current; if (!mainEl) { return {}; } const win = ownerWindow(mainEl); const computedStyle = win.getComputedStyle(mainEl); const newHeight = Math.floor(parseFloat(computedStyle.height)) || 0; const newWidth = Math.floor(parseFloat(computedStyle.width)) || 0; store.update(prev => { if (prev.dimensions.width === newWidth && prev.dimensions.height === newHeight) { return prev; } return _extends({}, prev, { dimensions: { margin: { top: params.margin.top, right: params.margin.right, bottom: params.margin.bottom, left: params.margin.left }, width: params.width ?? newWidth, height: params.height ?? newHeight, propsWidth: params.width, propsHeight: params.height } }); }); return { height: newHeight, width: newWidth }; }, [store, svgRef, params.height, params.width, // Margin is an object, so we need to include all the properties to prevent infinite loops. params.margin.left, params.margin.right, params.margin.top, params.margin.bottom]); React.useEffect(() => { store.update(prev => { const width = params.width ?? prev.dimensions.width; const height = params.height ?? prev.dimensions.height; return _extends({}, prev, { dimensions: { margin: { top: params.margin.top, right: params.margin.right, bottom: params.margin.bottom, left: params.margin.left }, width, height, propsHeight: params.height, propsWidth: params.width } }); }); }, [store, params.height, params.width, // Margin is an object, so we need to include all the properties to prevent infinite loops. params.margin.left, params.margin.right, params.margin.top, params.margin.bottom]); React.useEffect(() => { // Ensure the error detection occurs after the first rendering. stateRef.current.displayError = true; }, []); // This effect is used to compute the size of the container on the initial render. // It is not bound to the raf loop to avoid an unwanted "resize" event. // https://github.com/mui/mui-x/issues/13477#issuecomment-2336634785 useEnhancedEffect(() => { // computeRun is used to avoid infinite loops. if (hasInSize || !stateRef.current.initialCompute || stateRef.current.computeRun > MAX_COMPUTE_RUN) { return; } const computedSize = computeSize(); if (computedSize.width !== innerWidth || computedSize.height !== innerHeight) { stateRef.current.computeRun += 1; if (computedSize.width !== undefined) { setInnerWidth(computedSize.width); } if (computedSize.height !== undefined) { setInnerHeight(computedSize.height); } } else if (stateRef.current.initialCompute) { stateRef.current.initialCompute = false; } }, [innerHeight, innerWidth, computeSize, hasInSize]); useEnhancedEffect(() => { if (hasInSize) { return () => {}; } computeSize(); const elementToObserve = svgRef.current; if (typeof ResizeObserver === 'undefined') { return () => {}; } let animationFrame; const observer = new ResizeObserver(() => { // See https://github.com/mui/mui-x/issues/8733 animationFrame = requestAnimationFrame(() => { computeSize(); }); }); if (elementToObserve) { observer.observe(elementToObserve); } return () => { if (animationFrame) { cancelAnimationFrame(animationFrame); } if (elementToObserve) { observer.unobserve(elementToObserve); } }; }, [computeSize, hasInSize, svgRef]); if (process.env.NODE_ENV !== 'production') { if (stateRef.current.displayError && params.width === undefined && innerWidth === 0) { console.error(`MUI X Charts: ChartContainer does not have \`width\` prop, and its container has no \`width\` defined.`); stateRef.current.displayError = false; } if (stateRef.current.displayError && params.height === undefined && innerHeight === 0) { console.error(`MUI X Charts: ChartContainer does not have \`height\` prop, and its container has no \`height\` defined.`); stateRef.current.displayError = false; } } const drawingArea = useSelector(store, selectorChartDrawingArea); const isXInside = React.useCallback(x => x >= drawingArea.left - 1 && x <= drawingArea.left + drawingArea.width, [drawingArea.left, drawingArea.width]); const isYInside = React.useCallback(y => y >= drawingArea.top - 1 && y <= drawingArea.top + drawingArea.height, [drawingArea.height, drawingArea.top]); const isPointInside = React.useCallback((x, y, targetElement) => { // For element allowed to overflow, wrapping them in <g data-drawing-container /> make them fully part of the drawing area. if (targetElement && targetElement.closest('[data-drawing-container]')) { return true; } return isXInside(x) && isYInside(y); }, [isXInside, isYInside]); return { instance: { isPointInside, isXInside, isYInside } }; }; useChartDimensions.params = { width: true, height: true, margin: true }; useChartDimensions.getDefaultizedParams = ({ params }) => _extends({}, params, { margin: defaultizeMargin(params.margin, DEFAULT_MARGINS) }); useChartDimensions.getInitialState = ({ width, height, margin }) => { return { dimensions: { margin, width: width ?? 0, height: height ?? 0, propsWidth: width, propsHeight: height } }; };