Newer
Older
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
function DetailDonut(data, element) {
// chart dimensions
var width = 800;
var height = 800;
// a circle chart needs a radius
var radius = Math.min(width, height) / 2;
var donutWidth = 100; // size of donut hole. not needed if doing pie chart
// legend dimensions
var legendRectSize = 25; // defines the size of the colored squares in legend
var legendSpacing = 6; // defines spacing between squares
// define color scale
var color = d3.scaleOrdinal(d3.schemePuBu[9]);
// calculate new total
var total = d3.sum(data, d => d.value);
// define new total section
var newTotal = d3.select('.new-total-holder')
.append('span')
.attr('class', 'newTotal').text(total);
var svg = d3.select(element) // select element provided to the method
.append('svg') // append an svg element to the element we've selected
.attr('width', width) // set the width of the svg element we just added
.attr('height', height) // set the height of the svg element we just added
.append('g') // append 'g' element to the svg element
.attr('transform', 'translate(' + (width / 2) + ',' + (height / 2) + ')'); // our reference is now to the 'g' element. centerting the 'g' element to the svg element
var arc = d3.arc()
.innerRadius(radius - donutWidth) // radius - donutWidth = size of donut hole. use 0 for pie chart
.outerRadius(radius); // size of overall chart
var pie = d3.pie() // start and end angles of the segments
.value(function(d) { return d.value; }) // how to extract the numerical data from each entry in our dataset
.sort(null); // by default, data sorts in oescending value. this will mess with our animation so we set it to null
//**********************
// TOOLTIP
//**********************
//var tooltip = d3.select(element) // select element in the DOM provided to the method
// .append('div') // append a div element to the element we've selected
// .attr('class', 'tooltip'); // add class 'tooltip' on the divs we just selected
var tooltip = d3.select(element)
.append('div')
.attr('class', 'd3-tooltip')
.style('position', 'absolute')
.style('z-index', '10')
.style('visibility', 'hidden')
.style('padding', '10px')
.style('background', 'rgba(0,0,0,0.6)')
.style('border-radius', '4px')
.style('color', '#fff');
tooltip.append('div') // add divs to the tooltip defined above
.attr('class', 'label'); // add class 'label' on the selection
tooltip.append('div') // add divs to the tooltip defined above
.attr('class', 'value'); // add class 'value' on the selection
tooltip.append('div') // add divs to the tooltip defined above
.attr('class', 'percent'); // add class 'percent' on the selection
data.forEach(function(d) {
d.value = +d.value; // calculate value as we iterate through the data
d.enabled = true; // add enabled property to track which entries are checked
});
// creating the chart
var path = svg.selectAll('path') // select all path elements inside the svg. specifically the 'g' element. they don't exist yet but they will be created below
.data(pie(data)) //associate dataset wit he path elements we're about to create. must pass through the pie function. it magically knows how to extract values and bakes it into the pie
.enter() //creates placeholder nodes for each of the values
.append('path') // replace placeholders with path elements
.attr('d', arc) // define d attribute with arc function above
.attr('fill', function(d) { return color(d.data.label); }) // use color scale to define fill of each label in dataset
.each(function(d) { this._current - d; }); // creates a smooth animation for each track
// mouse event handlers are attached to path so they need to come after its definition
path.on('mouseover', function(event, d, i) { // when mouse enters div
var total = d3.sum(data.map(function(d) { // calculate the total number of tickets in the dataset
return (d.enabled) ? d.value : 0; // checking to see if the entry is enabled. if it isn't, we return 0 and cause other percentages to increase
}));
var percent = Math.round(1000 * d.data.value / total) / 10; // calculate percent
tooltip.select('.label').html('Agent: ' + d.data.label); // set current label
tooltip.select('.value').html('Value: ' + d.data.value); // set current value
tooltip.select('.percent').html('Percentage: ' + percent + '%'); // set percent calculated above
tooltip.style('visibility', 'visible'); // set visibility
});
path.on('mouseout', function(event, d) { // when mouse leaves div
tooltip.style('visibility', 'hidden'); // hide tooltip for that element
});
path.on('mousemove', function(event, d) { // when mouse moves
tooltip.style('top', (event.layerY + 10) + 'px') // always 10px below the cursor
.style('left', (event.layerX + 10) + 'px'); // always 10px to the right of the mouse
});
// define legend
var legend = svg.selectAll('.legend') // selecting elements with class 'legend'
.data(color.domain()) // refers to an array of labels from our dataset
.enter() // creates placeholder
.append('g') // replace placeholders with g elements
.attr('class', 'legend') // each g is given a legend class
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing; // height of element is the height of the colored square plus the spacing
var offset = height * color.domain().length / 2; // vertical offset of the entire legend = height of a single element & half the total number of elements
var horz = -2 * legendRectSize; // the legend is shifted to the left to make room for the text
var vert = i * height - offset; // the top of the element is hifted up or down from the center using the offset defiend earlier and the index of the current element 'i'
return 'translate(' + horz + ',' + vert + ')'; //return translation
});
// adding colored squares to legend
legend.append('rect') // append rectangle squares to legend
.attr('width', legendRectSize) // width of rect size is defined above
.attr('height', legendRectSize) // height of rect size is defined above
.style('fill', color) // each fill is passed a color
.style('stroke', color) // each stroke is passed a color
.on('click', function(label) {
var rect = d3.select(this); // this refers to the colored squared just clicked
var enabled = true; // set enabled true to default
var totalEnabled = d3.sum(data.map(function(d) { // can't disable all options
return (d.enabled) ? 1 : 0; // return 1 for each enabled entry. and summing it up
}));
if (rect.attr('class') === 'disabled') { // if class is disabled
rect.attr('class', ''); // remove class disabled
} else { // else
if (totalEnabled < 2) return; // if less than two labels are flagged, exit
rect.attr('class', 'disabled'); // otherwise flag the square disabled
enabled = false; // set enabled to false
}
pie.value(function(d) {
if (d.label === label) d.enabled = enabled; // if entry label matches legend label
return (d.enabled) ? d.value : 0; // update enabled property and return value or 0 based on the entry's status
});
path = path.data(pie(data)); // update pie with new data
path.transition() // transition of redrawn pie
.duration(750) //
.attrTween('d', function(d) { // 'd' specifies the d attribute that we'll be animating
var interpolate = d3.interpolate(this._current, d); // this = current path element
this._current = interpolate(0); // interpolate between current value and the new value of 'd'
return function(t) {
return arc(interpolate(t));
};
});
// calculate new total
var newTotalCalc = d3.sum(data.filter(function(d) { return d.enabled;}), d => d.value)
console.log(newTotalCalc);
// append newTotalCalc to newTotal which is defined above
newTotal.text(newTotalCalc);
});
// adding text to legend
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) { return d; }); // return label
}