<commit_before>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<CategoricalChartProps, CategoricalChartState> {
    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 <Axis />
            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)) {
        if (/.*touch.*/i.test(eventName)) {
          e = e.changedTouches[0];
        }

        const mouse = this.getMouseInfo(e);<commit_msg>Revert "(Fix) Add touch event transformation for outer events handler"<commit_after>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<CategoricalChartProps, CategoricalChartState> {
    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 <Axis />
            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);
{'fraction_non_alphanumeric': 0.11183828634516668, 'fraction_numerical': 0.0019918278150217964, 'mean_word_length': 2.146848137535817, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 111, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3209726', 'n_tokens_mistral': 24799, 'n_tokens_neox': 24244, 'n_words': 5994}