src/FunnelChart.js (105 lines of code) (raw):
import { area } from 'd3-shape';
import { scaleOrdinal } from 'd3-scale';
import { schemeCategory10 } from 'd3-scale-chromatic';
import range from 'lodash/range';
import defaults from 'lodash/defaults';
import PropTypes from 'prop-types';
import React from 'react';
import * as CustomPropTypes from './utils/CustomPropTypes';
import {
combineDomains,
domainFromData,
getValue,
makeAccessor2,
} from './utils/Data';
import { dataTypeFromScaleType } from './utils/Scale';
import xyPropsEqual from './utils/xyPropsEqual';
/**
* `FunnelChart` is used to visualize the progressive reduction of data as it passes
* from one phase to another.
*/
export default class FunnelChart extends React.Component {
static propTypes = {
/**
* Array of data to be plotted.
*/
data: PropTypes.array.isRequired,
/**
* Accessor function for X values, called once per datum, or a single value to be used for all data.
*/
x: CustomPropTypes.valueOrAccessor,
/**
* Accessor function for Y values, called once per datum, or a single value to be used for all data.
*/
y: CustomPropTypes.valueOrAccessor,
/**
* Color applied to the path element,
* or accessor function which returns a class.
*
* Note that the first datum's color would not be applied since it fills in the area of the path
*/
color: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
/**
* Boolean which determines whether the chart will be horizontal.
*/
horizontal: PropTypes.bool,
/**
* Classname applied to each path element,
* or accessor function which returns a class.
*/
pathClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
/**
* Style applied to each path element,
* or accessor function which returns a style object.
*/
pathStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
/**
* D3 scale for X axis - provided by XYPlot.
*/
xScale: PropTypes.func,
/**
* D3 scale for Y axis - provided by XYPlot.
*/
yScale: PropTypes.func,
};
static defaultProps = {
pathClassName: '',
pathStyle: {},
};
static getDomain(props) {
const { data, xScaleType, yScaleType, x, y, horizontal } = props;
const [xAccessor, yAccessor] = [makeAccessor2(x), makeAccessor2(y)];
const [xDataType, yDataType] = [
dataTypeFromScaleType(xScaleType),
dataTypeFromScaleType(yScaleType),
];
return horizontal
? {
xDomain: combineDomains([
domainFromData(data, xAccessor, xDataType),
domainFromData(data, (d, i) => -xAccessor(d, i), xDataType),
]),
yDomain: domainFromData(data, yAccessor, yDataType),
}
: {
xDomain: domainFromData(data, xAccessor, xDataType),
yDomain: combineDomains([
domainFromData(data, yAccessor, yDataType),
domainFromData(data, (d, i) => -yAccessor(d, i), yDataType),
]),
};
}
shouldComponentUpdate(nextProps) {
const shouldUpdate = !xyPropsEqual(this.props, nextProps, []);
return shouldUpdate;
}
render() {
const {
data,
xScale,
yScale,
color,
pathStyle,
x,
y,
horizontal,
pathClassName,
} = this.props;
const funnelArea = area();
if (horizontal) {
funnelArea
.x0((d, i) => xScale(-getValue(x, d, i)))
.x1((d, i) => xScale(getValue(x, d, i)))
.y((d, i) => yScale(getValue(y, d, i)));
} else {
funnelArea
.x((d, i) => xScale(getValue(x, d, i)))
.y0((d, i) => yScale(-getValue(y, d, i)))
.y1((d, i) => yScale(getValue(y, d, i)));
}
const colors = scaleOrdinal(schemeCategory10).domain(range(10));
return (
<g className="rct-funnel-chart" aria-hidden="true">
{data.map((d, i) => {
if (i === 0) return null;
const pathStr = funnelArea([data[i - 1], d]);
const fill = color ? getValue(color, d, i) : colors(i - 1);
let style = getValue(pathStyle, d, i);
style = defaults({}, style, { fill, stroke: 'transparent' });
return (
<path
d={pathStr}
className={`${getValue(pathClassName, d, i) || ''}`}
style={style}
key={i}
/>
);
})}
</g>
);
}
}