/
FunnelChart.js
151 lines (141 loc) · 4.35 KB
/
FunnelChart.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
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>
);
}
}