import React from 'react'; import { Customized } from 'recharts'; import { CustomTooltip } from './general/CustomTooltip'; import AxisDragUtil from './utils/AxisDragUtil'; import { formatTimeSeriesTicks, uid, getData, getXAxisKey, getDomainOfDataByKey, convertXAxisToNumber } from './utils/helpers'; import SelectionUtil from './utils/SelectionUtil'; import TooltipUtil from './utils/TooltipUtil'; import { withFading } from './utils/withFading'; import { withResponsiveContainer } from './utils/withResponsiveContainer'; export interface ToolkitProps { children: JSX.Element | JSX.Element[] | string; width: number; height: number; } export type Props = ToolkitProps; export enum ActionState { NONE, ZOOMSELECT, ZOOMIN, ZOOMOUT, PAN, LASOSELECT, AUTOSCALE, CAMERA, DRAW } export enum TooltipState { CLOSEST = 'closest', COMPARE = 'compare' } export const Toolkit = withResponsiveContainer((props: ToolkitProps) => { const toolkit_graph_ref = React.useRef(`toolkit_ref_${uid()}`); const toolbarRef = React.useRef(null); const containerRef = React.useRef(null); const graphStatetRef = React.useRef(null); const [parent, setParent] = React.useState(null); const [toolbarComponents, setToolbarComponents] = React.useState(null); const [yAxisDomainMap, setYAxisDomainMap]: any = React.useState({0: ['auto', 'auto']}); const [xAxisDomainMap, setXAxisDomainMap]: any = React.useState({0: ['auto', 'auto']}); const originalResolvedDomainX = React.useRef(null); const originalResolvedDomainY = React.useRef(null); const [selectState, setSelectState] = React.useState(ActionState.NONE); const [selectCoords, setSelectCoords] = React.useState(null); const [panState, setPanState] = React.useState(false); const [hasSet, setHasSet] = React.useState(false) //circular const [tooltipMode, setTooltipMode] = React.useState(TooltipState.CLOSEST); const [tooltipCoord, setTooltipCoord] = React.useState(null); const [enableReferenceLines, setEnableReferenceLines] = React.useState(false); const [ticks, setTicks] = React.useState([]); const [drawType, setDrawType] = React.useState('polygon'); React.useEffect(() => { const setComponents = (child: any) => { switch (child.type.name) { case 'CategoricalChartWrapper': setParent(child); break; case 'ToolBar': setToolbarComponents(child); break; default: if (child.type.name == null && child.props.children instanceof Object) { if (child.props.children?.type.name == "CategoricalChartWrapper") { setParent(child.props.children) } } break; //TODO handle non svg elements } } if (props.children instanceof Array) { props.children.forEach(child => { setComponents(child); }) } else if (props.children instanceof Object) { setComponents(props.children); } else { setParent(null); console.warn("CategoricalChartWrapper is not a child of Toolkit.") } }, [props.children, props.width, props.height]); //TODO remove React.useEffect(() => { if (parent ) { parent.props.children .filter((component: any) => component.type.name == 'XAxis' || component.type.name == 'YAxis') .forEach((axisElement: any, index: number) => { if (axisElement.type.name == "XAxis") { if ( typeof xAxisDomainMap[axisElement.props.xAxisId][0] == 'string' || typeof xAxisDomainMap[axisElement.props.xAxisId][1] == 'string' ) { const xAxis = getDomainOfDataByKey( getData(parent), getXAxisKey(parent, axisElement.props.xAxisId), axisElement.props.type ); //TODO if (axisElement.props.type != 'category') { let tmp = [], x1 = xAxis[0], x2 = xAxis[1], tickCount = 5; for (let numTick = 0; numTick < tickCount; numTick++) { tmp.push((x1+(((x2-x1)/tickCount)*numTick))); } setTicks(tmp); //TODO by axis id } //TODO setXAxisDomainMap({...xAxisDomainMap, [axisElement.props.xAxisId]: xAxis}); } } else if (axisElement.type.name == "YAxis") { //TODO setYAxisDomainMap({...yAxisDomainMap, [axisElement.props.yAxisId]: axisElement.props.domain}) } }); } }, [parent]) const proxy = () => ( { React.useEffect(() => { //TODO big issue graphStatetRef.current = customizedProps; if (hasSet == false) { setHasSet(true); originalResolvedDomainX.current = customizedProps.xAxisMap; originalResolvedDomainY.current = customizedProps.yAxisMap; } }, [customizedProps]) return null; }}/> ); const children = () => parent && React.Children.map( parent.props.children.concat( [ proxy(), ] ), child => { if (child == null) { return; } const xAxisConfig = (child: any) => ({ allowDataOverflow: true, domain: xAxisDomainMap[child.props.xAxisId], scale: 'linear', type: 'number', ticks: child.props.type != 'category' ? ticks : null, interval: child.props.type != 'category' ? 0 : 0, tickFormatter: (tick: any) => { if (child.props.type == "time") { const date = new Date(tick.toFixed(0)*1000); const interval = ((xAxisDomainMap[child.props.xAxisId][1]-xAxisDomainMap[child.props.xAxisId][0])/5); return formatTimeSeriesTicks(interval, date); } else if (child.props.type == 'category') { return parent.props.data[tick].name } else { return tick } } }); if (child.type) { switch (child.type.displayName) { case "YAxis": return React.cloneElement(child, { allowDataOverflow: true, domain: yAxisDomainMap[child.props.yAxisId], allowDecimals: false, tickFormatter: (tick: any) => parseInt(tick), scale: 'linear', }) case "XAxis": return React.cloneElement(child, xAxisConfig(child)) //TODO disable dots when moving case "Line": return React.cloneElement(child, { }) case "Area": return React.cloneElement(child, { }) default: return React.cloneElement(child) }; } else { return React.cloneElement(child) } } ); return (
{parent && withFading({ element:
{(graphStatetRef.current && hasSet) && React.cloneElement( toolbarComponents, { customizedProps: graphStatetRef.current, }, React.Children.map(toolbarComponents.props.children, child => { return React.cloneElement( child, { ...graphStatetRef.current, data: parent.props.data, containerRef: containerRef.current, graph_uid: toolkit_graph_ref.current, yAxisDomainMap, setYAxisDomainMap, xAxisDomainMap, setXAxisDomainMap, selectState, setSelectState, selectCoords, setSelectCoords, panState, setPanState, tooltipMode, setTooltipMode, enableReferenceLines, setEnableReferenceLines, ticks, setTicks, drawType, setDrawType } ); }) ) }
, duration: 0.5, isOut: false }) } { parent && React.cloneElement( parent, { id: toolkit_graph_ref.current, width: props.width || parent.props.width, height: props.height || parent.props.height, data: convertXAxisToNumber(parent.props.data) }, children() ) } {(graphStatetRef.current && hasSet) && //TODO: needs to be component { setTooltipCoord(payload); }, setEnableReferenceLines, enableReferenceLines, }}/> { //TODO disable tooltip, hide toolbar setYAxisDomainMap({...yAxisDomainMap, [axisId]: domain}); }, onCoordXChange: (domain: any, axisId: any) => { //TODO disable tooltip, hide toolbar setXAxisDomainMap({...xAxisDomainMap, [axisId]: domain}); }, onBatchCoordYChange: (domainMap: any) => { setYAxisDomainMap({...yAxisDomainMap, ...domainMap}); }, onBatchCoordXChange: (domainMap: any) => { setXAxisDomainMap({...xAxisDomainMap, ...domainMap}); }, ticks, setTicks, //everything gets converted to type 'number' originalXAxisType: (xAxisId: number) => parent.props.children.filter((n: any) => n.type.name == 'XAxis' && n.props.xAxisId == xAxisId)[0].props.type, originalResolvedDomainX: originalResolvedDomainX.current, originalResolvedDomainY: originalResolvedDomainY.current, }}/> {selectState && setSelectCoords(coords), setSelectState, yAxisDomainMap, xAxisDomainMap, drawType, offsetLeft: containerRef.current ? containerRef.current.offsetLeft : 0 }} />} } {/* tooltip layer */} {tooltipCoord && }
); });