/** * @fileOverview Wrapper component to make charts adapt to the size of parent * DOM */ import classNames from 'classnames'; import _ from 'lodash'; import React, { ReactElement, forwardRef, cloneElement, useState, useImperativeHandle, useRef, useEffect } from 'react'; import ReactResizeDetector from 'react-resize-detector'; import { isPercent } from '../util/DataUtils'; import { warn } from '../util/LogUtils'; export interface Props { aspect?: number; width?: string | number; height?: string | number; minWidth?: string | number; minHeight?: string | number; maxHeight?: number; children: ReactElement; debounce?: number; id?: string | number; className?: string | number; } interface State { containerWidth: number; containerHeight: number; } export const ResponsiveContainer = forwardRef( ( { aspect, width = '100%', height = '100%', minWidth, minHeight, maxHeight, children, debounce = 0, id, className, }: Props, ref, ) => { const [sizes, setSizes] = useState({ containerWidth: -1, containerHeight: -1, }); const containerRef = useRef(null); useImperativeHandle(ref, () => containerRef, [containerRef]); const [mounted, setMounted] = useState(false); const getContainerSize = () => { if (!containerRef.current) { return null; } return { containerWidth: containerRef.current.clientWidth, containerHeight: containerRef.current.clientHeight, }; }; const updateDimensionsImmediate = () => { if (!mounted) { return; } const newSize = getContainerSize(); if (newSize) { const { containerWidth: oldWidth, containerHeight: oldHeight } = sizes; const { containerWidth, containerHeight } = newSize; if (containerWidth !== oldWidth || containerHeight !== oldHeight) { setSizes({ containerWidth, containerHeight }); } } }; const handleResize = debounce > 0 ? _.debounce(updateDimensionsImmediate, debounce) : updateDimensionsImmediate; const renderChart = () => { const { containerWidth, containerHeight } = sizes; if (containerWidth < 0 || containerHeight < 0) { return null; } warn( isPercent(width) || isPercent(height), `The width(%s) and height(%s) are both fixed numbers, maybe you don't need to use a ResponsiveContainer.`, width, height, ); warn(!aspect || aspect > 0, 'The aspect(%s) must be greater than zero.', aspect); let calculatedWidth: number = isPercent(width) ? containerWidth : (width as number); let calculatedHeight: number = isPercent(height) ? containerHeight : (height as number); if (aspect && aspect > 0) { // Preserve the desired aspect ratio if (calculatedWidth) { // Will default to using width for aspect ratio calculatedHeight = calculatedWidth / aspect; } else if (calculatedHeight) { // But we should also take height into consideration calculatedWidth = calculatedHeight * aspect; } // if maxHeight is set, overwrite if calculatedHeight is greater than maxHeight if (maxHeight && calculatedHeight > maxHeight) { calculatedHeight = maxHeight; } } warn( calculatedWidth > 0 || calculatedHeight > 0, `The width(%s) and height(%s) of chart should be greater than 0, please check the style of container, or the props width(%s) and height(%s), or add a minWidth(%s) or minHeight(%s) or use aspect(%s) to control the height and width.`, calculatedWidth, calculatedHeight, width, height, minWidth, minHeight, aspect, ); return cloneElement(children, { width: calculatedWidth, height: calculatedHeight, }); }; useEffect(() => { if (mounted) { const size = getContainerSize(); if (size) { setSizes(size); } } }, [mounted]); useEffect(() => { setMounted(true); }, []); const style = { width, height, minWidth, minHeight, maxHeight }; return (
{renderChart()}
); }, );