modules_Chart_AreaChart_index.js

import { classnames, PropTypes, React } from '@gravityforms/libraries';
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
import Box from '../../../elements/Box';
import Checkbox from '../../../elements/Checkbox';
import DataDot from '../common/DataDot';
import CustomTooltip from '../common/Tooltip';

const { forwardRef, useState, useEffect } = React;

/**
 * @module GravityAreaChart
 * @description The AreaChart component. Loaded by the Chart renderer and displayed by passing type "area".
 *
 * @since 4.4.5
 *
 * @param {number}              animationDuration  The duration of the reveal animation in milliseconds.
 * @param {object}              areaProps          The props to pass to each Area (can also override the presets we use). Check Recharts docs for all available.
 * @param {object}              cartesianGridProps The props to pass to the CartesianGrid (can also override the presets we use). Check Recharts docs for all available.
 * @param {object}              checkboxProps      The props to pass to each Checkbox. Check our docs for all available.
 * @param {node}                children           Any additional content to display below the chart.
 * @param {string}              cursorColor        The color of the cursor line in the chart that appears when the tooltip is hovered.
 * @param {object}              customAttributes   Custom attributes to apply to the chart wrapper.
 * @param {string|array|object} customClasses      Custom classes to apply to the chart wrapper.
 * @param {array}               data               The data to display in the chart. Check Recharts docs for formats.
 * @param {string}              gridColor          The color of the grid lines and the x and y axis.
 * @param {number|string}       height             The height of the chart.
 * @param {object}              legendProps        The props to pass to the Legend (can also override the presets we use). Check Recharts docs for all available.
 * @param {array}               options            The options to display as checkboxes to toggle the visibility of each data set.
 * @param {boolean}             showCheckboxes     Whether to show the checkboxes to toggle the visibility of each data set.
 * @param {boolean}             showLegend         Whether to show the legend.
 * @param {object}              tooltipProps       The props to pass to the Tooltip (can also override the presets we use). Check Recharts docs for all available.
 * @param {number|string}       width              The width of the chart.
 * @param {object}              xAxisProps         The props to pass to the XAxis (can also override the presets we use). Check Recharts docs for all available.
 * @param {object}              yAxisProps         The props to pass to the YAxis (can also override the presets we use). Check Recharts docs for all available.
 * @param {object}              ref                The reference to the chart component.
 *
 * @return {JSX.Element|null} The AreaChart component.
 */
const GravityAreaChart = forwardRef( ( {
	animationDuration = 1000,
	areaProps = {},
	cartesianGridProps = {},
	checkboxProps = {},
	children = null,
	cursorColor = '#9092b2',
	customAttributes = {},
	customClasses = {},
	customInterval = null,
	data = [],
	gridColor = '#ecedf8',
	height = 400,
	legendProps = {},
	options = [],
	showCheckboxes = true,
	showLegend = false,
	tooltipProps = {},
	width = '100%',
	xAxisProps = {},
	yAxisProps = {},
}, ref ) => {
	const componentProps = {
		className: classnames( {
			'gform-chart--area': true,
			'gform-chart__wrapper': true,
		}, customClasses ),
		...customAttributes,
		ref,
	};
	const initialCheckboxState = options.reduce( ( acc, option ) => {
		acc[ option.dataKey ] = option.defaultChecked || true; // Set default visibility, default to true
		return acc;
	}, {} );

	const [ checkboxState, setCheckboxState ] = useState( initialCheckboxState );
	const [ animationActive, setAnimationActive ] = useState( true );
	const [ interval, setInterval ] = useState( Math.floor( data.length / 10 ) );

	const updateInterval = () => {
		const screenWidth = window.innerWidth;
		setInterval( Math.floor( data.length / ( screenWidth / 180 ) ) );
	};

	useEffect( () => {
		// Disable animation after the first render
		const timer = setTimeout( () => {
			setAnimationActive( false );
		}, animationDuration );

		return () => clearTimeout( timer );
	}, [] );

	useEffect( () => {
		if ( customInterval ) {
			customInterval( setInterval, data.length );
		} else {
			updateInterval();
			window.addEventListener( 'resize', updateInterval );

			return () => {
				window.removeEventListener( 'resize', updateInterval );
			};
		}
	}, [ data.length, customInterval ] );

	const handleCheckboxChange = ( dataKey ) => {
		setCheckboxState( ( prevState ) => ( {
			...prevState,
			[ dataKey ]: ! prevState[ dataKey ],
		} ) );
	};

	const cartesianGridAttributes = {
		stroke: gridColor,
		strokeDasharray: '0',
		vertical: false,
		...cartesianGridProps,
	};

	const yAxisAttributes = {
		axisLine: { stroke: gridColor },
		padding: { top: 10 },
		tickLine: { stroke: gridColor },
		...yAxisProps,
	};

	const xAxisAttributes = {
		axisLine: { stroke: gridColor },
		tickLine: { stroke: gridColor },
		interval,
		...xAxisProps,
	};

	const tooltipAttributes = {
		content: <CustomTooltip />,
		cursor: { stroke: cursorColor },
		...tooltipProps,
	};

	const legendAttributes = {
		...legendProps,
	};

	return (
		<div { ...componentProps }>
			{ showCheckboxes && (
				<div className="gform-chart__checkboxes">
					<Box display="flex" spacing={ [ 0, 5, 0, 0 ] }>
						{ options.map( ( option ) => {
							const checkboxAttributes = {
								externalChecked: checkboxState[ option.dataKey ],
								externalControl: true,
								labelAttributes: {
									label: option.label,
									weight: 'normal',
								},
								onChange: () => handleCheckboxChange( option.dataKey ),
								spacing: [ 0, 0, 4, 3 ],
								...checkboxProps,
							};
							return (
								<Checkbox key={ option.dataKey } { ...checkboxAttributes } />
							);
						} ) }
					</Box>
				</div>
			) }
			<ResponsiveContainer width={ width } height={ height }>
				<AreaChart data={ data } margin={ { top: 0, right: 20, left: 0, bottom: 0 } }>
					<defs>
						{ options.map( ( option ) => (
							<linearGradient key={ option.dataKey } id={ `color${ option.dataKey }` } x1="0" y1="0" x2="0" y2="1">
								<stop offset="5%" stopColor={ option.color } stopOpacity={ 0.1 } />
								<stop offset="95%" stopColor={ option.color } stopOpacity={ 0 } />
							</linearGradient>
						) ) }
					</defs>
					<CartesianGrid { ...cartesianGridAttributes } />
					<XAxis { ...xAxisAttributes } />
					<YAxis { ...yAxisAttributes } />
					<Tooltip { ...tooltipAttributes } />
					{ showLegend && <Legend { ...legendAttributes } /> }
					{ options.map(
						( option ) => {
							const areaAttributes = {
								type: 'monotone',
								dataKey: option.dataKey,
								stroke: option.color,
								fillOpacity: 1,
								fill: `url(#color${ option.dataKey })`,
								strokeWidth: 2,
								dot: false,
								activeDot: <DataDot stroke={ option.color } />,
								isAnimationActive: animationActive,
								animationBegin: 0,
								animationDuration,
								...areaProps,
							};
							return checkboxState[ option.dataKey ] ? (
								<Area key={ option.dataKey } { ...areaAttributes } />
							) : null;
						}
					) }
				</AreaChart>
			</ResponsiveContainer>
			{ children }
		</div>
	);
} );

GravityAreaChart.propTypes = {
	animationDuration: PropTypes.number,
	areaProps: PropTypes.object,
	cartesianGridProps: PropTypes.object,
	checkboxProps: PropTypes.object,
	children: PropTypes.oneOfType( [
		PropTypes.arrayOf( PropTypes.node ),
		PropTypes.node,
	] ),
	cursorColor: PropTypes.string,
	customAttributes: PropTypes.object,
	customClasses: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.array,
		PropTypes.object,
	] ),
	data: PropTypes.array,
	gridColor: PropTypes.string,
	height: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number ] ),
	legendProps: PropTypes.object,
	options: PropTypes.array,
	showCheckboxes: PropTypes.bool,
	showLegend: PropTypes.bool,
	tooltipProps: PropTypes.object,
	width: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number ] ),
	xAxisProps: PropTypes.object,
	yAxisProps: PropTypes.object,
};

GravityAreaChart.displayName = 'AreaChart';

export default GravityAreaChart;