text: import {
    assign,
    defaults,
    flatten,
    isFunction,
    uniq,
    some,
    groupBy,
    uniqBy,
    values,
    isPlainObject
  } from "lodash";
  import React from "react";
  import Axis from "./axis";
  import Style from "./style";
  import Transitions from "./transitions";
  import Data from "./data";
  import Domain from "./domain";
  import Events from "./events";
  import Collection from "./collection";
  import Helpers from "./helpers";
  import Scale from "./scale";
  import Log from "./log";
  
  export default {
    getData(props, childComponents) {
      if (props.data) {
        return Data.getData(props);
      }
      childComponents = childComponents || React.Children.toArray(props.children);
      return this.getDataFromChildren(childComponents);
    },
  
    getDefaultDomainPadding(props, axis, childComponents) {
      if (props.polar || axis !== "x") {
        return undefined;
      }
      const groupComponent = childComponents.filter((child) => {
        return child.type && child.type.role && child.type.role === "group";
      });
  
      if (groupComponent.length < 1) {
        return undefined;
      }
      const { offset, children } = groupComponent[0].props;
      return { x: (offset * children.length) / 2 };
    },
  
    getDomain(props, axis, childComponents) {
      childComponents = childComponents || React.Children.toArray(props.children);
  
      const propsDomain = Domain.getDomainFromProps(props, axis);
      const domainPadding = this.getDefaultDomainPadding(props, axis, childComponents);
      let domain;
  
      if (propsDomain) {
        domain = propsDomain;
      } else {
        const minDomain = Domain.getMinFromProps(props, axis);
        const maxDomain = Domain.getMaxFromProps(props, axis);
        const dataset = (props.data || props.y) && Data.getData(props);
        const dataDomain = dataset ? Domain.getDomainFromData(props, axis, dataset) : [];
        const childDomain = this.getDomainFromChildren(props, axis, childComponents);
        const min = minDomain || Collection.getMinValue([...dataDomain, ...childDomain]);
        const max = maxDomain || Collection.getMaxValue([...dataDomain, ...childDomain]);
        domain = Domain.getDomainFromMinMax(min, max);
      }
      return Domain.formatDomain(domain, assign({ domainPadding }, props), axis);
    },
  
    getScale(props, axis, childComponents) {
      if (props.data) {
        return Scale.getBaseScale(props, axis);
      }
      const children = childComponents
        ? childComponents.slice(0)
        : React.Children.toArray(props.children);
      const iteratee = (child) => {
        const sharedProps = assign({}, child.props, { horizontal: props.horizontal });
        return Scale.getScaleType(sharedProps, axis);
      };
      const childScale = uniq(Helpers.reduceChildren(children, iteratee, props));
      // default to linear scale if more than one uniq scale type is given by children
  
      return childScale.length > 1
        ? Scale.getScaleFromName("linear")
        : Scale.getScaleFromName(childScale[0]);
    },
  
    setAnimationState(props, nextProps) {
      if (!props.animate) {
        return;
      }
      if (props.animate.parentState) {
        const nodesWillExit = props.animate.parentState.nodesWillExit;
        const oldProps = nodesWillExit ? props : null;
        this.setState(defaults({ oldProps, nextProps }, props.animate.parentState));
      } else {
        const oldChildren = React.Children.toArray(props.children);
        const nextChildren = React.Children.toArray(nextProps.children);
        const isContinuous = (child) => {
          const check = (c) => c.type && c.type.continuous;
          return Array.isArray(child) ? some(child, check) : check(child);
        };
  
        const continuous =
          !props.polar &&
          some(oldChildren, (child) => {
            return (
              isContinuous(child) || (child.props.children && isContinuous(child.props.children))
            );
          });
        const {
          nodesWillExit,
          nodesWillEnter,
          childrenTransitions,
          nodesShouldEnter
        } = Transitions.getInitialTransitionState(oldChildren, nextChildren);
  
        this.setState({
          nodesWillExit,
          nodesWillEnter,
          nodesShouldEnter,
          childrenTransitions: Collection.isArrayOfArrays(childrenTransitions)
            ? childrenTransitions[0]
            : childrenTransitions,
          oldProps: nodesWillExit ? props : null,
          nextProps,
          continuous
        });
      }
    },
  
    getAllEvents(props) {
      const components = ["groupComponent", "containerComponent", "labelComponent"];
      this.componentEvents = Events.getComponentEvents(props, components);
      let events = props.events;
      if (Array.isArray(this.componentEvents)) {
        events = Array.isArray(props.events)
          ? this.componentEvents.concat(...props.events)
          : this.componentEvents;
      }
      return events || [];
    },
  
    getAnimationProps(props, child, index) {
      if (!props.animate) {
        return child.props.animate;
      }
      const getFilteredState = () => {
        let childrenTransitions = this.state && this.state.childrenTransitions;
        childrenTransitions = Collection.isArrayOfArrays(childrenTransitions)
          ? childrenTransitions[index]
          : childrenTransitions;
        return defaults({ childrenTransitions }, this.state);
      };
  
      let getTransitions = props.animate && props.animate.getTransitions;
      const state = getFilteredState();
      const parentState = (props.animate && props.animate.parentState) || state;
      if (!getTransitions) {
        const getTransitionProps = Transitions.getTransitionPropsFactory(props, state, (newState) =>
          this.setState(newState)
        );
        getTransitions = (childComponent) => getTransitionProps(childComponent, index);
      }
      return defaults({ getTransitions, parentState }, props.animate, child.props.animate);
    },
  
    getDomainFromChildren(props, axis, childComponents) {
      // eslint-disable-line max-statements, complexity, max-len
      const children = childComponents
        ? childComponents.slice(0)
        : React.Children.toArray(props.children);
      const parentData = props.data ? Data.getData(props, axis) : undefined;
      const { polar, startAngle, endAngle, categories, minDomain, maxDomain, horizontal } = props;
      const baseParentProps = {
        horizontal,
        polar,
        startAngle,
        endAngle,
        minDomain,
        maxDomain,
        categories
      };
      const parentProps = parentData
        ? assign(baseParentProps, { data: parentData })
        : baseParentProps;
  
      const iteratee = (child) => {
        const sharedProps = assign({}, child.props, parentProps);
        if (!Domain.isDomainComponent(child)) {
          return null;
        } else if (child.type && isFunction(child.type.getDomain)) {
          return child.props && child.type.getDomain(sharedProps, axis);
        } else {
          return Domain.getDomain(sharedProps, axis);
        }
      };
  
      const childDomains = Helpers.reduceChildren(children, iteratee, props);
      const min = childDomains.length === 0 ? 0 : Collection.getMinValue(childDomains);
      const max = childDomains.length === 0 ? 1 : Collection.getMaxValue(childDomains);
      return [min, max];
    },
  
    addBinsToParentPropsIfHistogram({ children, props, childComponents, parentProps }) {
      const someChildrenAreHistograms = children.some((child) => {
        return child.type && child.type.role === "histogram";
      });
  
      const allChildrenAreHistograms =
        someChildrenAreHistograms &&
        children.length &&
        children.every((child) => {
          return child.type && child.type.role === "histogram";
        });
  
      if (someChildrenAreHistograms && !allChildrenAreHistograms) {
        Log.warn(
          "VictoryHistogram only supports being stacked with other VictoryHistogram components. Check to make sure that you are only passing VictoryHistogram components to VictoryStack"
        );
      }
  
      // if we are stacking histograms, we need to generate explicit bins
      // or else each histogram may end up having different bins
      if (!allChildrenAreHistograms) {
        return parentProps;
      }
  
      let childBins = props.bins || childComponents[0].props.bins;
  
      // if we have explicit bins then we don't need to calculate them
      if (!Array.isArray(childBins)) {
        const combinedData = children.reduce((memo, child) => {
          const xAccessor = Helpers.createAccessor(child.props.x || "x");
          return memo.concat(child.props.data.map((datum) => ({ x: xAccessor(datum) })));
        }, []);
  
        // use the same function to generate bins as VictoryHistogram but with
        // the combined data from above, then get explicit bins from that
        const getFormattedHistogramData = children[0].type.getFormattedData;
        childBins = getFormattedHistogramData({ data: combinedData, bins: childBins }).reduce(
          (memo, { x0, x1 }, index) => (index === 0 ? memo.concat([x0, x1]) : memo.concat(x1)),
          []
        );
      }
  
      return { ...parentProps, bins: childBins };
    },
  
    getDataFromChildren(props, childComponents) {
      const { polar, startAngle, endAngle, categories, minDomain, maxDomain } = props;
      let parentProps = { polar, startAngle, endAngle, categories, minDomain, maxDomain };
      let stack = 0;
      const children = childComponents
        ? childComponents.slice(0)
        : React.Children.toArray(props.children);
  
      parentProps = this.addBinsToParentPropsIfHistogram({
        children,
        props,
        childComponents,
        parentProps
      });
  
      const iteratee = (child, childName, parent) => {
        const childProps = assign({}, child.props, parentProps);
        let childData;
        if (!Data.isDataComponent(child)) {
          return null;
        } else if (child.type && isFunction(child.type.getData)) {
          child = parent ? React.cloneElement(child, parent.props) : child;
          childData = child.type.getData(childProps);
        } else {
          childData = Data.getData(childProps);
        }
        stack += 1;
        return childData.map((datum, index) => assign({ _stack: stack, _group: index }, datum));
      };
  
      const stacked = children.filter((c) => c.type && c.type.role === "stack").length;
      const combine = (memo, val) => memo.concat(uniqBy(val, "_group"));
      const datasets = Helpers.reduceChildren(children, iteratee, props, [], combine);
      const group = stacked ? "_group" : "_stack";
      return values(groupBy(datasets, group));
    },
  
    getColor(calculatedProps, child, index) {
      // check for styles first
      const { style } = calculatedProps;
      let { colorScale, color } = calculatedProps;
      if (style && style.data && style.data.fill) {
        return style.data.fill;
      }
      colorScale = child.props && child.props.colorScale ? child.props.colorScale : colorScale;
      color = child.props && child.props.color ? child.props.color : color;
      if (!colorScale && !color) {
        return undefined;
      }
      const colors = Array.isArray(colorScale) ? colorScale : Style.getColorScale(colorScale);
      return color || colors[index % colors.length];
    },
  
    getWidth(props) {
      const { datasets, scale, horizontal } = props;
      const range = horizontal ? scale.y.range() : scale.x.range();
      const extent = Math.abs(range[1] - range[0]);
      const seriesLength = Array.isArray(datasets[0]) ? datasets[0].length : 1;
      const bars = datasets.length * seriesLength + 2;
      const barRatio = 0.5;
      return { width: Math.round((barRatio * extent) / bars) };
    },
  
    getStyle(theme, style, role) {
      const defaultStyle = theme && theme[role] && theme[role].style ? theme[role].style : {};
      return Helpers.getStyles(style, defaultStyle);
    },
  
    getChildStyle(child, index, calculatedProps) {
      const { style, role } = calculatedProps;
      const childStyle = child.props.style || {};
      if (Array.isArray(childStyle)) {
        return childStyle;
      }
      const childRole = child.type && child.type.role;
      const defaultFill =
        childRole === "stack" ? undefined : this.getColor(calculatedProps, child, index);
      const defaultColor =
        childRole === "line" ? { fill: "none", stroke: defaultFill } : { fill: defaultFill };
      const dataWidth = role === "stack" ? {} : this.getWidth(calculatedProps);
      const dataStyle = defaults(
        {},
        childStyle.data,
        assign({}, dataWidth, style.data, defaultColor)
      );
      const labelsStyle = defaults({}, childStyle.labels, style.labels);
      return {
        parent: style.parent,
        data: dataStyle,
        labels: labelsStyle
      };
    },
  
    getStringsFromCategories(childComponents, axis) {
      const iteratee = (child) => {
        const childProps = child.props || {};
        if (!Domain.isDomainComponent(child) || !childProps.categories) {
          return null;
        } else {
          const categories =
            childProps.categories && !Array.isArray(childProps.categories)
              ? childProps.categories[axis]
              : childProps.props.categories;
          const categoryStrings = categories && categories.filter((val) => typeof val === "string");
          return categoryStrings ? Collection.removeUndefined(categoryStrings) : [];
        }
      };
      return Helpers.reduceChildren(childComponents.slice(0), iteratee);
    },
  
    getStringsFromData(childComponents) {
      const iteratee = (child) => {
        const childProps = child.props || {};
        let data;
        if (!Data.isDataComponent(child)) {
          return null;
        } else if (child.type && isFunction(child.type.getData)) {
          data = child.type.getData(childProps);
        } else {
          data = Data.getData(childProps);
        }
        return data.map((d) => ({ x: d.xName, y: d.yName }));
      };
  
      const initialMemo = { x: [], y: [] };
      const combine = (memo, datum) => {
        const x = Array.isArray(datum) ? datum.map((d) => d.x).filter(Boolean) : datum.x;
        const y = Array.isArray(datum) ? datum.map((d) => d.y).filter(Boolean) : datum.y;
        return {
          x: x !== undefined ? memo.x.concat(x) : memo.x,
          y: y !== undefined ? memo.y.concat(y) : memo.y
        };
      };
      return Helpers.reduceChildren(childComponents.slice(0), iteratee, {}, initialMemo, combine);
    },
  
    getCategoryAndAxisStringsFromChildren(props, axis, childComponents) {
      const categories = isPlainObject(props.categories) ? props.categories[axis] : props.categories;
      const axisComponent = Axis.getAxisComponent(childComponents, axis);
      const axisStrings = axisComponent ? Data.getStringsFromAxes(axisComponent.props, axis) : [];
      const categoryStrings = categories || this.getStringsFromCategories(childComponents, axis);
      return uniq(flatten([...categoryStrings, ...axisStrings]));
    },
  
    getStringsFromChildren(props, childComponents) {
      childComponents = childComponents || React.Children.toArray(props.children);
  
      const xStrings = this.getCategoryAndAxisStringsFromChildren(props, "x", childComponents);
      const yStrings = this.getCategoryAndAxisStringsFromChildren(props, "y", childComponents);
  
      const dataStrings = this.getStringsFromData(childComponents);
  
      return {
        x: uniq(flatten([...xStrings, ...dataStrings.x])),
        y: uniq(flatten([...yStrings, ...dataStrings.y]))
      };
    },
  
    getCategories(props, childComponents, allStrings) {
      const xPropCategories =
        props.categories && !Array.isArray(props.categories) ? props.categories.x : props.categories;
  
      const yPropCategories =
        props.categories && !Array.isArray(props.categories) ? props.categories.y : props.categories;
  
      const fallbackRequired = !xPropCategories || !yPropCategories;
  
      const fallbackProps = fallbackRequired
        ? allStrings || this.getStringsFromChildren(props, childComponents)
        : {};
  
      const xCategories = xPropCategories || fallbackProps.x;
      const yCategories = yPropCategories || fallbackProps.y;
  
      return {
        x: xCategories.length > 0 ? xCategories : undefined,
        y: yCategories.length > 0 ? yCategories : undefined
      };
    }
  };
  