forked from smilli/d3-funnel-charts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
d3-funnel-charts.js
122 lines (101 loc) · 4.54 KB
/
d3-funnel-charts.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
function FunnelChart(data, width, height, bottomPct) {
/* Parameters:
data:
Array containing arrays of categories and engagement in order from greatest expected funnel engagement to lowest.
I.e. Button loads -> Short link hits
Ex: [['Button Loads', 1500], ['Button Clicks', 300], ['Subscribers', 150], ['Shortlink Hits', 100]]
width & height:
Optional parameters for width & height of chart in pixels, otherwise default width/height are used
bottomPct:
Optional parameter that specifies the percent of the total width the bottom of the trapezoid is
This is used to calculate the slope, so the chart's view can be changed by changing this value
*/
var DEFAULT_HEIGHT = 400,
DEFAULT_WIDTH = 600,
DEFAULT_BOTTOM_PERCENT = 1/3;
this.data = data;
this.totalEngagement = 0;
for(var i = 0; i < data.length; i++){
this.totalEngagement += data[i][1];
}
this.width = typeof width !== 'undefined' ? width : DEFAULT_WIDTH;
this.height = typeof height !== 'undefined' ? height : DEFAULT_HEIGHT;
this.bottomPct = typeof bottomPct !== 'undefined' ? bottomPct : DEFAULT_BOTTOM_PERCENT;
this.slope = 2*this.height/(this.width - this.bottomPct*this.width);
this.totalArea = (this.width+this.bottomPct*this.width)*this.height/2;
}
FunnelChart.prototype.getLabel = function(ind){
/* Get label of a category at index 'ind' in this.data */
return this.data[ind][0]
}
FunnelChart.prototype.getEngagementCount = function(ind){
/* Get engagement value of a category at index 'ind' in this.data */
return this.data[ind][1]
}
FunnelChart.prototype.createPaths = function(){
/* Returns an array of points that can be passed into d3.svg.line to create a path for the funnel */
trapezoids = []
function findNextPoints(chart, prevLeftX, prevRightX, prevHeight, dataInd){
// reached end of funnel
if(dataInd >= chart.data.length) return;
// math to calculate coordinates of the next base
area = chart.data[dataInd][1]*chart.totalArea/chart.totalEngagement;
prevBaseLength = prevRightX - prevLeftX;
nextBaseLength = Math.sqrt((chart.slope * prevBaseLength * prevBaseLength - 4 * area)/chart.slope);
nextLeftX = (prevBaseLength - nextBaseLength)/2 + prevLeftX;
nextRightX = prevRightX - (prevBaseLength-nextBaseLength)/2;
nextHeight = chart.slope * (prevBaseLength-nextBaseLength)/2 + prevHeight;
points = [[nextRightX, nextHeight]]
points.push([prevRightX, prevHeight]);
points.push([prevLeftX, prevHeight]);
points.push([nextLeftX, nextHeight]);
points.push([nextRightX, nextHeight]);
trapezoids.push(points);
findNextPoints(chart, nextLeftX, nextRightX, nextHeight, dataInd+1);
}
findNextPoints(this, 0, this.width, 0, 0);
return trapezoids;
}
FunnelChart.prototype.draw = function(elem, speed){
var DEFAULT_SPEED = 2.5
speed = typeof speed !== 'undefined' ? speed : DEFAULT_SPEED;
var funnelSvg = d3.select(elem).append('svg')
.attr('width', this.width)
.attr('height', this.height)
.append('g');
// Creates the correct d3 line for the funnel
var funnelPath = d3.svg.line()
.x(function(d) { return d[0]; })
.y(function(d) { return d[1]; });
// Automatically generates colors for each trapezoid in funnel
var colorScale = d3.scale.category10()
var paths = this.createPaths();
function drawTrapezoids(funnel, i){
var trapezoid = funnelSvg
.append('svg:path')
.attr('d', function(d){ return funnelPath([paths[i][0], paths[i][1], paths[i][2], paths[i][2], paths[i][1], paths[i][2]])})
.attr('fill', '#fff');
nextHeight = paths[i][[paths[i].length]-1];
var totalLength = trapezoid.node().getTotalLength();
var transition = trapezoid
.transition()
.duration(totalLength/speed)
.ease("linear")
.attr("d", function(d){return funnelPath(paths[i])})
.attr("fill", function(d){return colorScale(i)});
funnelSvg
.append('text')
.text(funnel.getLabel(i) + ': ' + funnel.getEngagementCount(i))
.attr("x", function(d){ return funnel.width/2 })
.attr("y", function(d){ return (paths[i][0][1] + paths[i][1][1])/2}) // Average height of bases
.attr("text-anchor", "middle")
.attr("dominant-baseline", "middle")
.attr("fill", "#fff");
if(i < paths.length - 1){
transition.each('end', function(){
drawTrapezoids(funnel, i+1)
})
}
}
drawTrapezoids(this, 0);
}