import React, { Component, cloneElement, isValidElement, createElement } from 'react'; import classNames from 'classnames'; import _ from 'lodash'; import Surface from '../container/Surface'; import Layer from '../container/Layer'; import Tooltip from '../component/Tooltip'; import Legend from '../component/Legend'; import Curve from '../shape/Curve'; import Cross from '../shape/Cross'; import Sector from '../shape/Sector'; import Dot from '../shape/Dot'; import Rectangle from '../shape/Rectangle'; import { findAllByType, findChildByType, getDisplayName, parseChildIndex, validateWidthHeight, isChildrenEqual, renderByOrder, getReactEventByType, } from '../util/ReactUtils'; import CartesianAxis from '../cartesian/CartesianAxis'; import Brush from '../cartesian/Brush'; import { getOffset, calculateChartCoordinate } from '../util/DOMUtils'; import { getAnyElementOfObject, hasDuplicate, uniqueId, isNumber, findEntryInArray } from '../util/DataUtils'; import { calculateActiveTickIndex, getMainColorOfGraphicItem, getBarSizeList, getBarPosition, appendOffsetOfLegend, getLegendProps, combineEventHandlers, getTicksOfAxis, getCoordinatesOfGrid, getStackedDataOfItem, parseErrorBarsOfAxis, getBandSizeOfAxis, getStackGroupsByAxisId, getValueByDataKey, isCategoricalAxis, getDomainOfItemsWithSameAxis, getDomainOfStackGroups, getDomainOfDataByKey, parseSpecifiedDomain, parseDomainOfCategoryAxis, } from '../util/ChartUtils'; import { detectReferenceElementsDomain } from '../util/DetectReferenceElementsDomain'; import { inRangeOfSector, polarToCartesian } from '../util/PolarUtils'; import { shallowEqual } from '../util/ShallowEqual'; import { eventCenter, SYNC_EVENT } from '../util/Events'; import { LayoutType, CategoricalChartOptions, Margin, ViewBox, ChartOffset, BaseAxisProps, Coordinate, ChartCoordinate, TickItem, filterProps, adaptEventHandlers, } from '../util/types'; const ORIENT_MAP = { xAxis: ['bottom', 'top'], yAxis: ['left', 'right'], }; const originCoordinate: Coordinate = { x: 0, y: 0 }; export interface CategoricalChartState { chartX?: number; chartY?: number; dataStartIndex?: number; dataEndIndex?: number; activeTooltipIndex?: number; isTooltipActive?: boolean; updateId?: number; xAxisMap?: { [k: string]: BaseAxisProps; }; yAxisMap?: { [k: string]: BaseAxisProps; }; orderedTooltipTicks?: any; tooltipAxis?: BaseAxisProps; tooltipTicks?: TickItem[]; graphicalItems?: any; activeCoordinate?: ChartCoordinate; offset?: ChartOffset; angleAxisMap?: any; radiusAxisMap?: any; formatedGraphicalItems?: any; /** active tooltip payload */ activePayload?: any[]; tooltipAxisBandSize?: number; /** active item */ activeItem?: any; /** Active label of data */ activeLabel?: string; xValue?: number; yValue?: number; } export interface CategoricalChartProps { syncId?: number | string; compact?: boolean; width?: number; height?: number; data?: any[]; layout?: LayoutType; stackOffset?: 'sign' | 'expand' | 'none' | 'wiggle' | 'silhouette'; throttleDelay?: number; margin?: Margin; barCategoryGap?: number | string; barGap?: number | string; barSize?: number | string; maxBarSize?: number; style?: any; className?: string; children?: any; defaultShowTooltip?: boolean; onClick?: any; onMouseLeave?: any; onMouseEnter?: any; onMouseMove?: any; onMouseDown?: any; onMouseUp?: any; reverseStackOrder?: boolean; id?: string; startAngle?: number; endAngle?: number; cx?: number | string; cy?: number | string; innerRadius?: number | string; outerRadius?: number | string; } const generateCategoricalChart = ({ chartName, GraphicalChild, eventType = 'axis', axisComponents, legendContent, formatAxisMap, defaultProps, }: CategoricalChartOptions) => class CategoricalChartWrapper extends Component { static displayName = chartName; uniqueChartId: any; clipPathId: any; legendInstance: any; // todo join specific chart propTypes static defaultProps: CategoricalChartProps = { layout: 'horizontal', stackOffset: 'none', barCategoryGap: '10%', barGap: 4, margin: { top: 5, right: 5, bottom: 5, left: 5 } as Margin, reverseStackOrder: false, ...defaultProps, }; /** * Returns default, reset state for the categorical chart. * @param {Object} props Props object to use when creating the default state * @return {Object} Whole new state */ static createDefaultState = (props: CategoricalChartProps): CategoricalChartState => { const { children, defaultShowTooltip } = props; const brushItem = findChildByType(children, Brush.displayName); const startIndex = (brushItem && brushItem.props && brushItem.props.startIndex) || 0; const endIndex = (brushItem && brushItem.props && brushItem.props.endIndex) || (props.data && props.data.length - 1) || 0; return { chartX: 0, chartY: 0, dataStartIndex: startIndex, dataEndIndex: endIndex, activeTooltipIndex: -1, isTooltipActive: !_.isNil(defaultShowTooltip) ? defaultShowTooltip : false, }; }; static hasBar = (graphicalItems: any[]): any[] | boolean => { if (!graphicalItems || !graphicalItems.length) { return false; } return graphicalItems.some((item: any) => { const name = getDisplayName(item && item.type); return name && name.indexOf('Bar') >= 0; }); }; static getDisplayedData = ( props: CategoricalChartProps, { graphicalItems, dataStartIndex, dataEndIndex }: any, item?: any, ): any[] => { const itemsData = (graphicalItems || []).reduce((result: any, child: any) => { const itemData = child.props.data; if (itemData && itemData.length) { return [...result, ...itemData]; } return result; }, []); if (itemsData && itemsData.length > 0) { return itemsData; } if (item && item.props && item.props.data && item.props.data.length > 0) { return item.props.data; } const { data } = props; if (data && data.length && isNumber(dataStartIndex) && isNumber(dataEndIndex)) { return data.slice(dataStartIndex, dataEndIndex + 1); } return []; }; container?: any; constructor(props: CategoricalChartProps) { super(props); const defaultState = CategoricalChartWrapper.createDefaultState(props); const updateId = 0; this.state = { ...defaultState, updateId: 0, ...this.updateStateOfAxisMapsOffsetAndStackGroups({ props, ...defaultState, updateId }), }; this.uniqueChartId = _.isNil(props.id) ? uniqueId('recharts') : props.id; this.clipPathId = `${this.uniqueChartId}-clip`; if (props.throttleDelay) { this.triggeredAfterMouseMove = _.throttle(this.triggeredAfterMouseMove, props.throttleDelay); } } /* eslint-disable react/no-did-mount-set-state */ componentDidMount() { if (!_.isNil(this.props.syncId)) { this.addListener(); } } // eslint-disable-next-line camelcase UNSAFE_componentWillReceiveProps(nextProps: CategoricalChartProps) { const { data, children, width, height, layout, stackOffset, margin } = this.props; const { updateId, isTooltipActive, activeItem, activeLabel, activePayload, activeCoordinate } = this.state; if ( nextProps.data !== data || nextProps.width !== width || nextProps.height !== height || nextProps.layout !== layout || nextProps.stackOffset !== stackOffset || !shallowEqual(nextProps.margin, margin) ) { const defaultState = CategoricalChartWrapper.createDefaultState(nextProps); this.setState({ ...defaultState, updateId: updateId + 1, ...this.updateStateOfAxisMapsOffsetAndStackGroups({ props: nextProps, ...defaultState, updateId: updateId + 1, }), }); } else if (!isChildrenEqual(nextProps.children, children)) { // update configuration in chilren const hasGlobalData = !_.isNil(nextProps.data); const newUpdateId = hasGlobalData ? updateId : updateId + 1; this.setState(prevState => ({ updateId: newUpdateId, ...this.updateStateOfAxisMapsOffsetAndStackGroups({ props: nextProps, ...prevState, updateId: newUpdateId, }), })); } // add syncId if (_.isNil(this.props.syncId) && !_.isNil(nextProps.syncId)) { this.addListener(); } // remove syncId if (!_.isNil(this.props.syncId) && _.isNil(nextProps.syncId)) { this.removeListener(); } } componentWillUnmount() { if (!_.isNil(this.props.syncId)) { this.removeListener(); } this.cancelThrottledTriggerAfterMouseMove(); } cancelThrottledTriggerAfterMouseMove() { if (typeof (this.triggeredAfterMouseMove as any).cancel === 'function') { (this.triggeredAfterMouseMove as any).cancel(); } } /** * Get the configuration of all x-axis or y-axis * @param {Object} props Latest props * @param {String} axisType The type of axis * @param {Array} graphicalItems The instances of item * @param {Object} stackGroups The items grouped by axisId and stackId * @param {Number} dataStartIndex The start index of the data series when a brush is applied * @param {Number} dataEndIndex The end index of the data series when a brush is applied * @return {Object} Configuration */ getAxisMap( props: CategoricalChartProps, { axisType = 'xAxis', AxisComp, graphicalItems, stackGroups, dataStartIndex, dataEndIndex }: any, ) { const { children } = props; const axisIdKey = `${axisType}Id`; // Get all the instance of Axis const axes = findAllByType(children, AxisComp); let axisMap = {}; if (axes && axes.length) { axisMap = this.getAxisMapByAxes(props, { axes, graphicalItems, axisType, axisIdKey, stackGroups, dataStartIndex, dataEndIndex, }); } else if (graphicalItems && graphicalItems.length) { axisMap = this.getAxisMapByItems(props, { Axis: AxisComp, graphicalItems, axisType, axisIdKey, stackGroups, dataStartIndex, dataEndIndex, }); } return axisMap; } /** * Get the configuration of axis by the options of axis instance * @param {Object} props Latest props * @param {Array} axes The instance of axes * @param {Array} graphicalItems The instances of item * @param {String} axisType The type of axis, xAxis - x-axis, yAxis - y-axis * @param {String} axisIdKey The unique id of an axis * @param {Object} stackGroups The items grouped by axisId and stackId * @param {Number} dataStartIndex The start index of the data series when a brush is applied * @param {Number} dataEndIndex The end index of the data series when a brush is applied * @return {Object} Configuration */ getAxisMapByAxes = ( props: CategoricalChartProps, { axes, graphicalItems, axisType, axisIdKey, stackGroups, dataStartIndex, dataEndIndex }: any, ) => { const { layout, children, stackOffset } = props; const isCategorical = isCategoricalAxis(layout, axisType); // Eliminate duplicated axes const axisMap = axes.reduce((result: any, child: any) => { const { type, dataKey, allowDataOverflow, allowDuplicatedCategory, scale, ticks } = child.props; const axisId = child.props[axisIdKey]; const displayedData = CategoricalChartWrapper.getDisplayedData(props, { graphicalItems: graphicalItems.filter((item: any) => item.props[axisIdKey] === axisId), dataStartIndex, dataEndIndex, }); const len = displayedData.length; if (!result[axisId]) { let domain, duplicateDomain, categoricalDomain; if (dataKey) { // has dataKey in domain = getDomainOfDataByKey(displayedData, dataKey, type); if (type === 'category' && isCategorical) { // the field type is category data and this axis is catrgorical axis const duplicate = hasDuplicate(domain); if (allowDuplicatedCategory && duplicate) { duplicateDomain = domain; // When category axis has duplicated text, serial numbers are used to generate scale domain = _.range(0, len); } else if (!allowDuplicatedCategory) { // remove duplicated category domain = parseDomainOfCategoryAxis(child.props.domain, domain, child).reduce( (finalDomain: any, entry: any) => finalDomain.indexOf(entry) >= 0 ? finalDomain : [...finalDomain, entry], [], ); } } else if (type === 'category') { // the field type is category data and this axis is numerical axis if (!allowDuplicatedCategory) { domain = parseDomainOfCategoryAxis(child.props.domain, domain, child).reduce( (finalDomain: any, entry: any) => finalDomain.indexOf(entry) >= 0 || entry === '' || _.isNil(entry) ? finalDomain : [...finalDomain, entry], [], ); } else { // eliminate undefined or null or empty string domain = domain.filter((entry: any) => entry !== '' && !_.isNil(entry)); } } else if (type === 'number') { // the field type is numerical const errorBarsDomain = parseErrorBarsOfAxis( displayedData, graphicalItems.filter((item: any) => item.props[axisIdKey] === axisId && !item.props.hide), dataKey, axisType, ); if (errorBarsDomain) { domain = errorBarsDomain; } } if (isCategorical && (type === 'number' || scale !== 'auto')) { categoricalDomain = getDomainOfDataByKey(displayedData, dataKey, 'category'); } } else if (isCategorical) { // the axis is a categorical axis domain = _.range(0, len); } else if (stackGroups && stackGroups[axisId] && stackGroups[axisId].hasStack && type === 'number') { // when stackOffset is 'expand', the domain may be calculated as [0, 1.000000000002] domain = stackOffset === 'expand' ? [0, 1] : getDomainOfStackGroups(stackGroups[axisId].stackGroups, dataStartIndex, dataEndIndex); } else { domain = getDomainOfItemsWithSameAxis( displayedData, graphicalItems.filter((item: any) => item.props[axisIdKey] === axisId && !item.props.hide), type, true, ); } if (type === 'number') { // To detect wether there is any reference lines whose props alwaysShow is true domain = detectReferenceElementsDomain(children, domain, axisId, axisType, ticks); if (child.props.domain) { domain = parseSpecifiedDomain(child.props.domain, domain, allowDataOverflow); } } else if (type === 'category' && child.props.domain) { const axisDomain = child.props.domain; const isDomainValidate = domain.every((entry: string | number) => axisDomain.indexOf(entry) >= 0); if (isDomainValidate) { domain = axisDomain; } } return { ...result, [axisId]: { ...child.props, axisType, domain, categoricalDomain, duplicateDomain, originalDomain: child.props.domain, isCategorical, layout, }, }; } return result; }, {}); return axisMap; }; /** * Get the configuration of axis by the options of item, * this kind of axis does not display in chart * @param {Object} props Latest props * @param {Array} graphicalItems The instances of item * @param {ReactElement} Axis Axis Component * @param {String} axisType The type of axis, xAxis - x-axis, yAxis - y-axis * @param {String} axisIdKey The unique id of an axis * @param {Object} stackGroups The items grouped by axisId and stackId * @param {Number} dataStartIndex The start index of the data series when a brush is applied * @param {Number} dataEndIndex The end index of the data series when a brush is applied * @return {Object} Configuration */ getAxisMapByItems = ( props: CategoricalChartProps, { graphicalItems, Axis, axisType, axisIdKey, stackGroups, dataStartIndex, dataEndIndex }: any, ) => { const { layout, children } = props; const displayedData = CategoricalChartWrapper.getDisplayedData(props, { graphicalItems, dataStartIndex, dataEndIndex, }); const len = displayedData.length; const isCategorical = isCategoricalAxis(layout, axisType); let index = -1; // The default type of x-axis is category axis, // The default contents of x-axis is the serial numbers of data // The default type of y-axis is number axis // The default contents of y-axis is the domain of data const axisMap = graphicalItems.reduce((result: any, child: any) => { const axisId = child.props[axisIdKey]; if (!result[axisId]) { index++; let domain; if (isCategorical) { domain = _.range(0, len); } else if (stackGroups && stackGroups[axisId] && stackGroups[axisId].hasStack) { domain = getDomainOfStackGroups(stackGroups[axisId].stackGroups, dataStartIndex, dataEndIndex); domain = detectReferenceElementsDomain(children, domain, axisId, axisType); } else { domain = parseSpecifiedDomain( Axis.defaultProps.domain, getDomainOfItemsWithSameAxis( displayedData, graphicalItems.filter((item: any) => item.props[axisIdKey] === axisId && !item.props.hide), 'number', ), Axis.defaultProps.allowDataOverflow, ); domain = detectReferenceElementsDomain(children, domain, axisId, axisType); } return { ...result, [axisId]: { axisType, ...Axis.defaultProps, hide: true, orientation: _.get(ORIENT_MAP, `${axisType}.${index % 2}`, null), domain, originalDomain: Axis.defaultProps.domain, isCategorical, layout, // specify scale when no Axis // scale: isCategorical ? 'band' : 'linear', }, }; } return result; }, {}); return axisMap; }; getActiveCoordinate(tooltipTicks: TickItem[], activeIndex: any, rangeObj: any): ChartCoordinate { const { layout } = this.props; const entry = tooltipTicks.find((tick: any) => tick && tick.index === activeIndex); if (entry) { if (layout === 'horizontal') { return { x: entry.coordinate, y: rangeObj.y }; } if (layout === 'vertical') { return { x: rangeObj.x, y: entry.coordinate }; } if (layout === 'centric') { const angle = entry.coordinate; const { radius } = rangeObj; return { ...rangeObj, ...polarToCartesian(rangeObj.cx, rangeObj.cy, radius, angle), angle, radius, }; } const radius = entry.coordinate; const { angle } = rangeObj; return { ...rangeObj, ...polarToCartesian(rangeObj.cx, rangeObj.cy, radius, angle), angle, radius, }; } return originCoordinate; } /** * Get the information of mouse in chart, return null when the mouse is not in the chart * @param {Object} event The event object * @return {Object} Mouse data */ getMouseInfo(event: any) { if (!this.container) { return null; } const containerOffset = getOffset(this.container); const e = calculateChartCoordinate(event, containerOffset); const rangeObj = this.inRange(e.chartX, e.chartY); if (!rangeObj) { return null; } const { xAxisMap, yAxisMap } = this.state; if (eventType !== 'axis' && xAxisMap && yAxisMap) { const xScale = getAnyElementOfObject(xAxisMap).scale; const yScale = getAnyElementOfObject(yAxisMap).scale; const xValue = xScale && xScale.invert ? xScale.invert(e.chartX) : null; const yValue = yScale && yScale.invert ? yScale.invert(e.chartY) : null; return { ...e, xValue, yValue }; } const { orderedTooltipTicks: ticks, tooltipAxis: axis, tooltipTicks } = this.state; const pos = this.calculateTooltipPos(rangeObj); const activeIndex = calculateActiveTickIndex(pos, ticks, tooltipTicks, axis); if (activeIndex >= 0 && tooltipTicks) { const activeLabel = tooltipTicks[activeIndex] && tooltipTicks[activeIndex].value; const activePayload = this.getTooltipContent(activeIndex, activeLabel); const activeCoordinate = this.getActiveCoordinate(ticks, activeIndex, rangeObj); return { ...e, activeTooltipIndex: activeIndex, activeLabel, activePayload, activeCoordinate, }; } return null; } /** * Get the content to be displayed in the tooltip * @param {Number} activeIndex Active index of data * @param {String} activeLabel Active label of data * @return {Array} The content of tooltip */ getTooltipContent(activeIndex: number, activeLabel?: string): any[] { const { graphicalItems, tooltipAxis } = this.state; const displayedData = CategoricalChartWrapper.getDisplayedData(this.props, this.state); if (activeIndex < 0 || !graphicalItems || !graphicalItems.length || activeIndex >= displayedData.length) { return null; } // get data by activeIndex when the axis don't allow duplicated category return graphicalItems.reduce((result: any, child: any) => { const { hide } = child.props; if (hide) { return result; } const { dataKey, name, unit, formatter, data, tooltipType } = child.props; let payload; if (tooltipAxis.dataKey && !tooltipAxis.allowDuplicatedCategory) { // graphic child has data props payload = findEntryInArray(data || displayedData, tooltipAxis.dataKey, activeLabel); } else { payload = (data && data[activeIndex]) || displayedData[activeIndex]; } if (!payload) { return result; } return [ ...result, { ...filterProps(child), dataKey, unit, formatter, name: name || dataKey, color: getMainColorOfGraphicItem(child), value: getValueByDataKey(payload, dataKey), type: tooltipType, payload, }, ]; }, []); } getFormatItems(props: CategoricalChartProps, currentState: any): any[] { const { graphicalItems, stackGroups, offset, updateId, dataStartIndex, dataEndIndex } = currentState; const { barSize, layout, barGap, barCategoryGap, maxBarSize: globalMaxBarSize } = props; const { numericAxisName, cateAxisName } = CategoricalChartWrapper.getAxisNameByLayout(layout); const hasBar = CategoricalChartWrapper.hasBar(graphicalItems); const sizeList = hasBar && getBarSizeList({ barSize, stackGroups }); const formatedItems = [] as any[]; const tooltipItem = findChildByType(props.children, Tooltip.displayName); const isTooltipTriggerByClick = tooltipItem && tooltipItem.props.trigger === 'click'; graphicalItems.forEach((item: any, index: number) => { const displayedData = CategoricalChartWrapper.getDisplayedData(props, { dataStartIndex, dataEndIndex }, item); const { dataKey, maxBarSize: childMaxBarSize } = item.props; const numericAxisId = item.props[`${numericAxisName}Id`]; const cateAxisId = item.props[`${cateAxisName}Id`]; const axisObj = axisComponents.reduce((result: any, entry: any) => { const axisMap = currentState[`${entry.axisType}Map`]; const id = item.props[`${entry.axisType}Id`]; const axis = axisMap && axisMap[id]; return { ...result, [entry.axisType]: axis, [`${entry.axisType}Ticks`]: getTicksOfAxis(axis), }; }, {}); const cateAxis = axisObj[cateAxisName]; const cateTicks = axisObj[`${cateAxisName}Ticks`]; const stackedData = stackGroups && stackGroups[numericAxisId] && stackGroups[numericAxisId].hasStack && getStackedDataOfItem(item, stackGroups[numericAxisId].stackGroups); const itemIsBar = getDisplayName(item.type).indexOf('Bar') >= 0; const bandSize = getBandSizeOfAxis(cateAxis, cateTicks); let barPosition = []; if (itemIsBar) { const barBandSize = getBandSizeOfAxis(cateAxis, cateTicks, true); // 如果是bar,计算bar的位置 const maxBarSize = _.isNil(childMaxBarSize) ? globalMaxBarSize : childMaxBarSize; barPosition = getBarPosition({ barGap, barCategoryGap, bandSize: barBandSize !== bandSize ? barBandSize : bandSize, sizeList: sizeList[cateAxisId], maxBarSize, }); if (barBandSize !== bandSize) { barPosition = barPosition.map((pos: { item: any; position: { offset: number; size: number } }) => ({ ...pos, position: { ...pos.position, offset: pos.position.offset - barBandSize / 2 }, })); } } const componsedFn = item && item.type && item.type.getComposedData; if (componsedFn) { const itemEvents = isTooltipTriggerByClick ? { onItemClick: combineEventHandlers(this.handleItemMouseEnter, null, item.props.onCLick), } : { onItemMouseLeave: combineEventHandlers(this.handleItemMouseLeave, null, item.props.onMouseLeave), onItemMouseEnter: combineEventHandlers(this.handleItemMouseEnter, null, item.props.onMouseEnter), }; formatedItems.push({ props: { ...componsedFn({ ...axisObj, displayedData, props, dataKey, item, bandSize, barPosition, offset, stackedData, layout, dataStartIndex, dataEndIndex, ...itemEvents, }), key: item.key || `item-${index}`, [numericAxisName]: axisObj[numericAxisName], [cateAxisName]: axisObj[cateAxisName], animationId: updateId, }, childIndex: parseChildIndex(item, props.children), item, }); } }); return formatedItems; } getCursorRectangle(): any { const { layout } = this.props; const { activeCoordinate, offset, tooltipAxisBandSize } = this.state; const halfSize = tooltipAxisBandSize / 2; return { stroke: 'none', fill: '#ccc', x: layout === 'horizontal' ? activeCoordinate.x - halfSize : offset.left + 0.5, y: layout === 'horizontal' ? offset.top + 0.5 : activeCoordinate.y - halfSize, width: layout === 'horizontal' ? tooltipAxisBandSize : offset.width - 1, height: layout === 'horizontal' ? offset.height - 1 : tooltipAxisBandSize, }; } getCursorPoints(): any { const { layout } = this.props; const { activeCoordinate, offset } = this.state; let x1, y1, x2, y2; if (layout === 'horizontal') { x1 = activeCoordinate.x; x2 = x1; y1 = offset.top; y2 = offset.top + offset.height; } else if (layout === 'vertical') { y1 = activeCoordinate.y; y2 = y1; x1 = offset.left; x2 = offset.left + offset.width; } else if (!_.isNil(activeCoordinate.cx) || !_.isNil(activeCoordinate.cy)) { if (layout === 'centric') { const { cx, cy, innerRadius, outerRadius, angle } = activeCoordinate; const innerPoint = polarToCartesian(cx, cy, innerRadius, angle); const outerPoint = polarToCartesian(cx, cy, outerRadius, angle); x1 = innerPoint.x; y1 = innerPoint.y; x2 = outerPoint.x; y2 = outerPoint.y; } else { const { cx, cy, radius, startAngle, endAngle } = activeCoordinate; const startPoint = polarToCartesian(cx, cy, radius, startAngle); const endPoint = polarToCartesian(cx, cy, radius, endAngle); return { points: [startPoint, endPoint], cx, cy, radius, startAngle, endAngle, }; } } return [ { x: x1, y: y1 }, { x: x2, y: y2 }, ]; } static getAxisNameByLayout(layout: LayoutType) { if (layout === 'horizontal') { return { numericAxisName: 'yAxis', cateAxisName: 'xAxis' }; } if (layout === 'vertical') { return { numericAxisName: 'xAxis', cateAxisName: 'yAxis' }; } if (layout === 'centric') { return { numericAxisName: 'radiusAxis', cateAxisName: 'angleAxis' }; } return { numericAxisName: 'angleAxis', cateAxisName: 'radiusAxis' }; } calculateTooltipPos(rangeObj: any): any { const { layout } = this.props; if (layout === 'horizontal') { return rangeObj.x; } if (layout === 'vertical') { return rangeObj.y; } if (layout === 'centric') { return rangeObj.angle; } return rangeObj.radius; } inRange(x: number, y: number): any { const { layout } = this.props; if (layout === 'horizontal' || layout === 'vertical') { const { offset } = this.state; const isInRange = x >= offset.left && x <= offset.left + offset.width && y >= offset.top && y <= offset.top + offset.height; return isInRange ? { x, y } : null; } const { angleAxisMap, radiusAxisMap } = this.state; if (angleAxisMap && radiusAxisMap) { const angleAxis = getAnyElementOfObject(angleAxisMap); return inRangeOfSector({ x, y }, angleAxis); } return null; } parseEventsOfWrapper() { const { children } = this.props; const tooltipItem = findChildByType(children, Tooltip.displayName); let tooltipEvents: any = {}; if (tooltipItem && eventType === 'axis') { if (tooltipItem.props.trigger === 'click') { tooltipEvents = { onClick: this.handleClick, }; } else { tooltipEvents = { onMouseEnter: this.handleMouseEnter, onMouseMove: this.handleMouseMove, onMouseLeave: this.handleMouseLeave, onTouchMove: this.handleTouchMove, onTouchStart: this.handleTouchStart, onTouchEnd: this.handleTouchEnd, }; } } const outerEvents = adaptEventHandlers(this.props, this.handleOuterEvent); return { ...outerEvents, ...tooltipEvents, }; } /** * The AxisMaps are expensive to render on large data sets * so provide the ability to store them in state and only update them when necessary * they are dependent upon the start and end index of * the brush so it's important that this method is called _after_ * the state is updated with any new start/end indices * * @param {Object} props The props object to be used for updating the axismaps * @param {Number} dataStartIndex The start index of the data series when a brush is applied * @param {Number} dataEndIndex The end index of the data series when a brush is applied * @param {Number} updateId The update id * @return {Object} state New state to set */ updateStateOfAxisMapsOffsetAndStackGroups({ props, dataStartIndex, dataEndIndex, updateId }: any): any { if (!validateWidthHeight({ props })) { return null; } const { children, layout, stackOffset, data, reverseStackOrder } = props; const { numericAxisName, cateAxisName } = CategoricalChartWrapper.getAxisNameByLayout(layout); const graphicalItems = findAllByType(children, GraphicalChild); const stackGroups = getStackGroupsByAxisId( data, graphicalItems, `${numericAxisName}Id`, `${cateAxisName}Id`, stackOffset, reverseStackOrder, ); const axisObj = axisComponents.reduce((result: any, entry: any) => { const name = `${entry.axisType}Map`; return { ...result, [name]: this.getAxisMap(props, { ...entry, graphicalItems, stackGroups: entry.axisType === numericAxisName && stackGroups, dataStartIndex, dataEndIndex, }), }; }, {}); const offset = this.calculateOffset({ ...axisObj, props, graphicalItems }); Object.keys(axisObj).forEach(key => { axisObj[key] = formatAxisMap(props, axisObj[key], offset, key.replace('Map', ''), chartName); }); const cateAxisMap = axisObj[`${cateAxisName}Map`]; const ticksObj = this.tooltipTicksGenerator(cateAxisMap); const formatedGraphicalItems = this.getFormatItems(props, { ...axisObj, dataStartIndex, dataEndIndex, updateId, graphicalItems, stackGroups, offset, }); return { formatedGraphicalItems, graphicalItems, offset, stackGroups, ...ticksObj, ...axisObj, }; } /* eslint-disable no-underscore-dangle */ addListener() { eventCenter.on(SYNC_EVENT, this.handleReceiveSyncEvent); if (eventCenter.setMaxListeners && eventCenter._maxListeners) { eventCenter.setMaxListeners(eventCenter._maxListeners + 1); } } removeListener() { eventCenter.removeListener(SYNC_EVENT, this.handleReceiveSyncEvent); if (eventCenter.setMaxListeners && eventCenter._maxListeners) { eventCenter.setMaxListeners(eventCenter._maxListeners - 1); } } /** * Calculate the offset of main part in the svg element * @param {Object} props Latest props * @param {Array} graphicalItems The instances of item * @param {Object} xAxisMap The configuration of x-axis * @param {Object} yAxisMap The configuration of y-axis * @return {Object} The offset of main part in the svg element */ calculateOffset({ props, graphicalItems, xAxisMap = {} as BaseAxisProps, yAxisMap = {} as BaseAxisProps }: any) { const { width, height, children } = props; const margin = props.margin || {}; const brushItem = findChildByType(children, Brush.displayName); const legendItem = findChildByType(children, Legend.displayName); const offsetH = Object.keys(yAxisMap).reduce( (result: any, id: any) => { const entry = yAxisMap[id]; const { orientation } = entry; if (!entry.mirror && !entry.hide) { return { ...result, [orientation]: result[orientation] + entry.width }; } return result; }, { left: margin.left || 0, right: margin.right || 0 }, ); const offsetV = Object.keys(xAxisMap).reduce( (result, id) => { const entry = xAxisMap[id]; const { orientation } = entry; if (!entry.mirror && !entry.hide) { return { ...result, [orientation]: _.get(result, `${orientation}`) + entry.height }; } return result; }, { top: margin.top || 0, bottom: margin.bottom || 0 }, ); let offset = { ...offsetV, ...offsetH }; const brushBottom = offset.bottom; if (brushItem) { offset.bottom += brushItem.props.height || Brush.defaultProps.height; } if (legendItem && this.legendInstance) { const legendBox = this.legendInstance.getBBox(); offset = appendOffsetOfLegend(offset, graphicalItems, props, legendBox); } return { brushBottom, ...offset, width: width - offset.left - offset.right, height: height - offset.top - offset.bottom, }; } handleLegendBBoxUpdate = (box: any) => { if (box && this.legendInstance) { const { dataStartIndex, dataEndIndex, updateId } = this.state; this.setState( this.updateStateOfAxisMapsOffsetAndStackGroups({ props: this.props, dataStartIndex, dataEndIndex, updateId, }), ); } }; handleReceiveSyncEvent = (cId: any, chartId: any, data: any) => { const { syncId, layout } = this.props; const { updateId } = this.state; if (syncId === cId && chartId !== this.uniqueChartId) { const { dataStartIndex, dataEndIndex } = data; if (!_.isNil(data.dataStartIndex) || !_.isNil(data.dataEndIndex)) { this.setState({ dataStartIndex, dataEndIndex, ...this.updateStateOfAxisMapsOffsetAndStackGroups({ props: this.props, dataStartIndex, dataEndIndex, updateId, }), }); } else if (!_.isNil(data.activeTooltipIndex)) { const { chartX, chartY, activeTooltipIndex } = data; const { offset, tooltipTicks } = this.state; if (!offset) { return; } const viewBox: ViewBox = { ...offset, x: offset.left, y: offset.top }; // When a categotical chart is combined with another chart, the value of chartX // and chartY may beyond the boundaries. const validateChartX = Math.min(chartX, viewBox.x + viewBox.width); const validateChartY = Math.min(chartY, viewBox.y + viewBox.height); const activeLabel = tooltipTicks[activeTooltipIndex] && tooltipTicks[activeTooltipIndex].value; const activePayload: any = this.getTooltipContent(activeTooltipIndex); const activeCoordinate = tooltipTicks[activeTooltipIndex] ? { x: layout === 'horizontal' ? tooltipTicks[activeTooltipIndex].coordinate : validateChartX, y: layout === 'horizontal' ? validateChartY : tooltipTicks[activeTooltipIndex].coordinate, } : originCoordinate; this.setState({ ...data, activeLabel, activeCoordinate, activePayload }); } else { this.setState(data); } } }; handleBrushChange = ({ startIndex, endIndex }: any) => { // Only trigger changes if the extents of the brush have actually changed if (startIndex !== this.state.dataStartIndex || endIndex !== this.state.dataEndIndex) { const { updateId } = this.state; this.setState(() => ({ dataStartIndex: startIndex, dataEndIndex: endIndex, ...this.updateStateOfAxisMapsOffsetAndStackGroups({ props: this.props, dataStartIndex: startIndex, dataEndIndex: endIndex, updateId, }), })); this.triggerSyncEvent({ dataStartIndex: startIndex, dataEndIndex: endIndex, }); } }; /** * The handler of mouse entering chart * @param {Object} e Event object * @return {Null} null */ handleMouseEnter = (e: any) => { const { onMouseEnter } = this.props; const mouse = this.getMouseInfo(e); if (mouse) { const nextState: CategoricalChartState = { ...mouse, isTooltipActive: true }; this.setState(nextState); this.triggerSyncEvent(nextState); if (_.isFunction(onMouseEnter)) { onMouseEnter(nextState, e); } } }; triggeredAfterMouseMove = (e: any): any => { const { onMouseMove } = this.props; const mouse = this.getMouseInfo(e); const nextState: CategoricalChartState = mouse ? { ...mouse, isTooltipActive: true } : { isTooltipActive: false }; this.setState(nextState); this.triggerSyncEvent(nextState); if (_.isFunction(onMouseMove)) { onMouseMove(nextState, e); } }; /** * The handler of mouse entering a scatter * @param {Object} el The active scatter * @return {Object} no return */ handleItemMouseEnter = (el: any) => { this.setState(() => ({ isTooltipActive: true, activeItem: el, activePayload: el.tooltipPayload, activeCoordinate: el.tooltipPosition || { x: el.cx, y: el.cy }, })); }; /** * The handler of mouse leaving a scatter * @return {Object} no return */ handleItemMouseLeave = () => { this.setState(() => ({ isTooltipActive: false, })); }; /** * The handler of mouse moving in chart * @param {Object} e Event object * @return {Null} no return */ handleMouseMove = (e: any) => { if (e && _.isFunction(e.persist)) { e.persist(); } this.triggeredAfterMouseMove(e); }; /** * The handler if mouse leaving chart * @param {Object} e Event object * @return {Null} no return */ handleMouseLeave = (e: any) => { const { onMouseLeave } = this.props; const nextState = { isTooltipActive: false }; this.setState(nextState); this.triggerSyncEvent(nextState); if (_.isFunction(onMouseLeave)) { onMouseLeave(nextState, e); } this.cancelThrottledTriggerAfterMouseMove(); }; handleOuterEvent = (e: any) => { const eventName = getReactEventByType(e); const event = _.get(this.props, `${eventName}`); if (eventName && _.isFunction(event)) { const mouse = this.getMouseInfo(e); const handler = event; handler(mouse, e); } }; handleClick = (e: any) => { const { onClick } = this.props; const mouse = this.getMouseInfo(e); if (mouse) { const nextState: CategoricalChartState = { ...mouse, isTooltipActive: true }; this.setState(nextState); this.triggerSyncEvent(nextState); if (_.isFunction(onClick)) { onClick(nextState, e); } } }; handleMouseDown = (e: any) => { const { onMouseDown } = this.props; if (_.isFunction(onMouseDown)) { const mouse = this.getMouseInfo(e); onMouseDown(mouse, e); } }; handleMouseUp = (e: any) => { const { onMouseUp } = this.props; if (_.isFunction(onMouseUp)) { const mouse = this.getMouseInfo(e); onMouseUp(mouse, e); } }; handleTouchMove = (e: any) => { if (e.changedTouches != null && e.changedTouches.length > 0) { this.handleMouseMove(e.changedTouches[0]); } }; handleTouchStart = (e: any) => { if (e.changedTouches != null && e.changedTouches.length > 0) { this.handleMouseDown(e.changedTouches[0]); } }; handleTouchEnd = (e: any) => { if (e.changedTouches != null && e.changedTouches.length > 0) { this.handleMouseUp(e.changedTouches[0]); } }; triggerSyncEvent(data: any) { const { syncId } = this.props; if (!_.isNil(syncId)) { eventCenter.emit(SYNC_EVENT, syncId, this.uniqueChartId, data); } } verticalCoordinatesGenerator = ({ xAxis, width, height, offset }: ChartCoordinate) => getCoordinatesOfGrid( CartesianAxis.getTicks({ ...CartesianAxis.defaultProps, ...xAxis, ticks: getTicksOfAxis(xAxis, true), viewBox: { x: 0, y: 0, width, height }, }), offset.left, offset.left + offset.width, ); horizontalCoordinatesGenerator = ({ yAxis, width, height, offset }: ChartCoordinate) => getCoordinatesOfGrid( CartesianAxis.getTicks({ ...CartesianAxis.defaultProps, ...yAxis, ticks: getTicksOfAxis(yAxis, true), viewBox: { x: 0, y: 0, width, height }, }), offset.top, offset.top + offset.height, ); axesTicksGenerator = (axis?: any) => getTicksOfAxis(axis, true); tooltipTicksGenerator = (axisMap: any) => { const axis: BaseAxisProps = getAnyElementOfObject(axisMap); const tooltipTicks = getTicksOfAxis(axis, false, true); return { tooltipTicks, orderedTooltipTicks: _.sortBy(tooltipTicks, o => o.coordinate), tooltipAxis: axis, tooltipAxisBandSize: getBandSizeOfAxis(axis), }; }; filterFormatItem(item: any, displayName: any, childIndex: any) { const { formatedGraphicalItems } = this.state; for (let i = 0, len = formatedGraphicalItems.length; i < len; i++) { const entry = formatedGraphicalItems[i]; if ( entry.item === item || entry.props.key === item.key || (displayName === getDisplayName(entry.item.type) && childIndex === entry.childIndex) ) { return entry; } } return null; } renderCursor = (element: any) => { const { isTooltipActive, activeCoordinate, activePayload, offset } = this.state; if (!element || !element.props.cursor || !isTooltipActive || !activeCoordinate) { return null; } const { layout } = this.props; let restProps; let cursorComp: any = Curve; if (chartName === 'ScatterChart') { restProps = activeCoordinate; cursorComp = Cross; } else if (chartName === 'BarChart') { restProps = this.getCursorRectangle(); cursorComp = Rectangle; } else if (layout === 'radial') { const { cx, cy, radius, startAngle, endAngle }: any = this.getCursorPoints(); restProps = { cx, cy, startAngle, endAngle, innerRadius: radius, outerRadius: radius, }; cursorComp = Sector; } else { restProps = { points: this.getCursorPoints() }; cursorComp = Curve; } const key = element.key || '_recharts-cursor'; const cursorProps = { stroke: '#ccc', pointerEvents: 'none', ...offset, ...restProps, ...filterProps(element.props.cursor), payload: activePayload, key, className: 'recharts-tooltip-cursor', }; return isValidElement(element.props.cursor) ? cloneElement(element.props.cursor, cursorProps) : createElement(cursorComp, cursorProps); }; renderPolarAxis = (element: any, displayName: string, index: number) => { const axisType = _.get(element, 'type.axisType'); const axisMap = _.get(this.state, `${axisType}Map`); const axisOption = axisMap[element.props[`${axisType}Id`]]; return cloneElement(element, { ...axisOption, className: axisType, key: element.key || `${displayName}-${index}`, ticks: getTicksOfAxis(axisOption, true), }); }; renderXAxis = (element: any, displayName: string, index: number) => { const { xAxisMap } = this.state; const axisObj = xAxisMap[element.props.xAxisId]; return this.renderAxis(axisObj, element, displayName, index); }; renderYAxis = (element: any, displayName: string, index: number) => { const { yAxisMap } = this.state; const axisObj = yAxisMap[element.props.yAxisId]; return this.renderAxis(axisObj, element, displayName, index); }; /** * Draw axis * @param {Object} axisOptions The options of axis * @param {Object} element The axis element * @param {String} displayName The display name of axis * @param {Number} index The index of element * @return {ReactElement} The instance of x-axes */ renderAxis(axisOptions: BaseAxisProps, element: any, displayName: string, index: number): React.ReactElement { const { width, height } = this.props; return ( ); } /** * Draw grid * @param {ReactElement} element the grid item * @return {ReactElement} The instance of grid */ renderGrid = (element: React.ReactElement): React.ReactElement => { const { xAxisMap, yAxisMap, offset } = this.state; const { width, height } = this.props; const xAxis = getAnyElementOfObject(xAxisMap); const yAxisWithFiniteDomain = _.find(yAxisMap, axis => _.every(axis.domain, Number.isFinite)); const yAxis = yAxisWithFiniteDomain || getAnyElementOfObject(yAxisMap); const props = element.props || {}; return cloneElement(element, { key: element.key || 'grid', x: isNumber(props.x) ? props.x : offset.left, y: isNumber(props.y) ? props.y : offset.top, width: isNumber(props.width) ? props.width : offset.width, height: isNumber(props.height) ? props.height : offset.height, xAxis, yAxis, offset, chartWidth: width, chartHeight: height, verticalCoordinatesGenerator: props.verticalCoordinatesGenerator || this.verticalCoordinatesGenerator, horizontalCoordinatesGenerator: props.horizontalCoordinatesGenerator || this.horizontalCoordinatesGenerator, }); }; renderPolarGrid = (element: React.ReactElement): React.ReactElement => { const { radiusAxisMap, angleAxisMap } = this.state; const radiusAxis = getAnyElementOfObject(radiusAxisMap); const angleAxis = getAnyElementOfObject(angleAxisMap); const { cx, cy, innerRadius, outerRadius } = angleAxis; return cloneElement(element, { polarAngles: getTicksOfAxis(angleAxis, true).map((entry: any) => entry.coordinate), polarRadius: getTicksOfAxis(radiusAxis, true).map((entry: any) => entry.coordinate), cx, cy, innerRadius, outerRadius, key: element.key || 'polar-grid', }); }; /** * Draw legend * @return {ReactElement} The instance of Legend */ renderLegend = (): React.ReactElement => { const { formatedGraphicalItems } = this.state; const { children, width, height } = this.props; const margin = this.props.margin || {}; const legendWidth: number = width - (margin.left || 0) - (margin.right || 0); const props = getLegendProps({ children, formatedGraphicalItems, legendWidth, legendContent, }); if (!props) { return null; } const { item, ...otherProps } = props; return cloneElement(item, { ...otherProps, chartWidth: width, chartHeight: height, margin, ref: (legend: any) => { this.legendInstance = legend; }, onBBoxUpdate: this.handleLegendBBoxUpdate, }); }; /** * Draw Tooltip * @return {ReactElement} The instance of Tooltip */ renderTooltip = (): React.ReactElement => { const { children } = this.props; const tooltipItem = findChildByType(children, Tooltip.displayName); if (!tooltipItem) { return null; } const { isTooltipActive, activeCoordinate, activePayload, activeLabel, offset } = this.state; return cloneElement(tooltipItem, { viewBox: { ...offset, x: offset.left, y: offset.top }, active: isTooltipActive, label: activeLabel, payload: isTooltipActive ? activePayload : [], coordinate: activeCoordinate, }); }; renderBrush = (element: React.ReactElement) => { const { margin, data } = this.props; const { offset, dataStartIndex, dataEndIndex, updateId } = this.state; // TODO: update brush when children update return cloneElement(element, { key: element.key || '_recharts-brush', onChange: combineEventHandlers(this.handleBrushChange, null, element.props.onChange), data, x: isNumber(element.props.x) ? element.props.x : offset.left, y: isNumber(element.props.y) ? element.props.y : offset.top + offset.height + offset.brushBottom - (margin.bottom || 0), width: isNumber(element.props.width) ? element.props.width : offset.width, startIndex: dataStartIndex, endIndex: dataEndIndex, updateId: `brush-${updateId}`, }); }; renderReferenceElement = (element: React.ReactElement, displayName: string, index: number): React.ReactElement => { if (!element) { return null; } const { clipPathId } = this; const { xAxisMap, yAxisMap, offset } = this.state; const { xAxisId, yAxisId } = element.props; return cloneElement(element, { key: element.key || `${displayName}-${index}`, xAxis: xAxisMap[xAxisId], yAxis: yAxisMap[yAxisId], viewBox: { x: offset.left, y: offset.top, width: offset.width, height: offset.height, }, clipPathId, }); }; static renderActiveDot(option: any, props: any): React.ReactElement { let dot; if (isValidElement(option)) { dot = cloneElement(option, props); } else if (_.isFunction(option)) { dot = option(props); } else { dot = ; } return ( {dot} ); } renderActivePoints = ({ item, activePoint, basePoint, childIndex, isRange }: any) => { const result = []; const { key } = item.props; const { activeDot, dataKey } = item.item.props; const dotProps = { index: childIndex, dataKey, cx: activePoint.x, cy: activePoint.y, r: 4, fill: getMainColorOfGraphicItem(item.item), strokeWidth: 2, stroke: '#fff', payload: activePoint.payload, value: activePoint.value, key: `${key}-activePoint-${childIndex}`, ...filterProps(activeDot), ...adaptEventHandlers(activeDot), }; result.push(CategoricalChartWrapper.renderActiveDot(activeDot, dotProps)); if (basePoint) { result.push( CategoricalChartWrapper.renderActiveDot(activeDot, { ...dotProps, cx: basePoint.x, cy: basePoint.y, key: `${key}-basePoint-${childIndex}`, }), ); } else if (isRange) { result.push(null); } return result; }; renderGraphicChild = (element: React.ReactElement, displayName: string, index: number): any[] => { const item = this.filterFormatItem(element, displayName, index); if (!item) { return null; } const graphicalItem = cloneElement(element, item.props); const { isTooltipActive, tooltipAxis, activeTooltipIndex, activeLabel } = this.state; const { children } = this.props; const tooltipItem = findChildByType(children, Tooltip.displayName); const { points, isRange, baseLine } = item.props; const { activeDot, hide } = item.item.props; const hasActive = !hide && isTooltipActive && tooltipItem && activeDot && activeTooltipIndex >= 0; function findWithPayload(entry: any) { // TODO needs to verify dataKey is Function return typeof tooltipAxis.dataKey === 'function' ? tooltipAxis.dataKey(entry.payload) : null; } if (hasActive) { let activePoint, basePoint; if (tooltipAxis.dataKey && !tooltipAxis.allowDuplicatedCategory) { // number transform to string const specifiedKey = typeof tooltipAxis.dataKey === 'function' ? findWithPayload : 'payload.'.concat(tooltipAxis.dataKey.toString()); activePoint = findEntryInArray(points, specifiedKey, activeLabel); basePoint = isRange && baseLine && findEntryInArray(baseLine, specifiedKey, activeLabel); } else { activePoint = points[activeTooltipIndex]; basePoint = isRange && baseLine && baseLine[activeTooltipIndex]; } if (!_.isNil(activePoint)) { return [ graphicalItem, ...this.renderActivePoints({ item, activePoint, basePoint, childIndex: activeTooltipIndex, isRange, }), ]; } } if (isRange) { return [graphicalItem, null, null]; } return [graphicalItem, null]; }; renderCustomized = (element: React.ReactElement): React.ReactElement => cloneElement(element, { ...this.props, ...this.state, }); renderClipPath() { const { clipPathId } = this; const { offset: { left, top, height, width }, } = this.state; return ( ); } render() { if (!validateWidthHeight(this)) { return null; } const { children, className, width, height, style, compact, ...others } = this.props; const attrs = filterProps(others); const map = { CartesianGrid: { handler: this.renderGrid, once: true }, ReferenceArea: { handler: this.renderReferenceElement }, ReferenceLine: { handler: this.renderReferenceElement }, ReferenceDot: { handler: this.renderReferenceElement }, XAxis: { handler: this.renderXAxis }, YAxis: { handler: this.renderYAxis }, Brush: { handler: this.renderBrush, once: true }, Bar: { handler: this.renderGraphicChild }, Line: { handler: this.renderGraphicChild }, Area: { handler: this.renderGraphicChild }, Radar: { handler: this.renderGraphicChild }, RadialBar: { handler: this.renderGraphicChild }, Scatter: { handler: this.renderGraphicChild }, Pie: { handler: this.renderGraphicChild }, Funnel: { handler: this.renderGraphicChild }, Tooltip: { handler: this.renderCursor, once: true }, PolarGrid: { handler: this.renderPolarGrid, once: true }, PolarAngleAxis: { handler: this.renderPolarAxis }, PolarRadiusAxis: { handler: this.renderPolarAxis }, Customized: { handler: this.renderCustomized }, } as any; // The "compact" mode is mainly used as the panorama within Brush if (compact) { return ( {this.renderClipPath()} {renderByOrder(children, map)} ); } const events = this.parseEventsOfWrapper(); return (
{ this.container = node; }} > {this.renderClipPath()} {renderByOrder(children, map)} {this.renderLegend()} {this.renderTooltip()}
); } }; export default generateCategoricalChart;