D3 Day - Week - Month Line Chart

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 9

25/4/2020 D3 Day/Week/Month Line Chart

cjhin / README.md
Last active 4 years ago • Report abuse
Embed <script src="https://gis Download ZIP

D3 Day/Week/Month Line Chart


README.md

D3 Time Series Day/Week/Month


Wanted to create a (daily) time series chart with automatic rollups to weekly/monthly lines AND nice transitions
Solution
The solution I ended up going with is a bit of an optical illusion.
All three views are technically backed by "daily" data.
The weekly and monthly views though have had their data modified to rollup to the first of the week/month, and
then all in between values are interpolated to create even slopes.
For example, given this daily data:
date, value
2014-01-01, 10
2014-01-02, 10
2014-01-03, 10
2014-01-04, 10
2014-01-05, 10
2014-01-06, 10
2014-01-07, 10

2014-01-08, 1
2014-01-09, 1
2014-01-10, 1
2014-01-11, 1
2014-01-12, 1
2014-01-13, 1
2014-01-14, 1

we first rollup to the first of the week


2014-01-01, 70
2014-01-02, 0
2014-01-03, 0
...
2014-01-08, 7
2014-01-09, 0
2014-01-10, 0

and then interpolate the remaining values


2014-01-01, 70
2014-01-02, 61
2014-01-03, 52
...
2014-01-08, 7

Issues
https://gist.github.com/cjhin/8872a492d44694ecf5a883642926f19c 1/9
25/4/2020 D3 Day/Week/Month Line Chart
One big down-side to this (besides the fact that the code isn't very simple) is that the various d3 interpolations
(line smoothing) won't work anymore.
If anyone knows of an easier way to do this ping me on github or leave a comment on the gist.
TODO:
something is off with the end of the month view, need to figure that out

data.csv

Search this file…


1 date value
2 2014-01-01 16
3 2014-01-02 8
4 2014-01-03 43
5 2014-01-04 21
6 2014-01-05 52
7 2014-01-06 47
8 2014-01-07 25
9 2014-01-08 33
10 2014-01-09 42
11 2014-01-10 55
12 2014-01-11 61
13 2014-01-12 61
14 2014-01-13 59
15 2014-01-14 40
16 2014-01-15 61
17 2014-01-16 75
18 2014-01-17 72
19 2014-01-18 71
20 2014-01-19 67
21 2014-01-20 69
22 2014-01-21 54
23 2014-01-22 47
24 2014-01-23 68
25 2014-01-24 52
26 2014-01-25 33
27 2014-01-26 55
28 2014-01-27 72
29 2014-01-28 96
30 2014-01-29 87
31 2014-01-30 95
32 2014-01-31 82
33 2014-02-01 70
34 2014-02-02 49
35 2014-02-03 22
36 2014-02-04 9
https://gist.github.com/cjhin/8872a492d44694ecf5a883642926f19c 2/9
25/4/2020 D3 Day/Week/Month Line Chart

1 date value
37 2014-02-05 13
38 2014-02-06 42
39 2014-02-07 56
40 2014-02-08 63
41 2014-02-09 72
42 2014-02-10 52
43 2014-02-11 49
44 2014-02-12 5
45 2014-02-13 1
46 2014-02-14 9
47 2014-02-15 63
48 2014-02-16 82
49 2014-02-17 55
50 2014-02-18 77
51 2014-02-19 67
52 2014-02-20 52
53 2014-02-21 64
54 2014-02-22 93
55 2014-02-23 93
56 2014-02-24 87
57 2014-02-25 55
58 2014-02-26 33
59 2014-02-27 37
60 2014-02-28 48
61 2014-03-01 49
62 2014-03-02 55
63 2014-03-03 65
64 2014-03-04 55
65 2014-03-05 33
66 2014-03-06 53
67 2014-03-07 55
68 2014-03-08 42
69 2014-03-09 45
70 2014-03-10 49
71 2014-03-11 49
72 2014-03-12 79
73 2014-03-13 71
74 2014-03-14 55
75 2014-03-15 63
76 2014-03-16 69
77 2014-03-17 61
78 2014-03-18 16
79 2014-03-19 21
80 2014-03-20 24
81 2014-03-21 29
82 2014-03-22 33
https://gist.github.com/cjhin/8872a492d44694ecf5a883642926f19c 3/9
25/4/2020 D3 Day/Week/Month Line Chart

1 date value
83 2014-03-23 39
84 2014-03-24 42
85 2014-03-25 42
86 2014-03-26 42
87 2014-03-27 43
88 2014-03-28 61
89 2014-03-29 55
90 2014-03-30 42
91 2014-03-31 48
92 2014-04-01 40
93 2014-04-02 35
94 2014-04-03 27
95 2014-04-04 28
96 2014-04-05 37
97 2014-04-06 41
98 2014-04-07 39
99 2014-04-08 30
100 2014-04-09 41
101 2014-04-10 47
102 2014-04-11 42
103 2014-04-12 45
104 2014-04-13 51
105 2014-04-14 46
106 2014-04-15 42
107 2014-04-16 25
108 2014-04-17 29
109 2014-04-18 27
110 2014-04-19 39
111 2014-04-20 42
112 2014-04-21 39
113 2014-04-22 37
114 2014-04-23 33
115 2014-04-24 37
116 2014-04-25 44
117 2014-04-26 10
118 2014-04-27 23
119 2014-04-28 30
120 2014-04-29 31
121 2014-04-30 52

index.html

1 <style>
2 body {
3 font-family: Helvetica Neue, Helvetica-Neue, Helvetica;
4 }
5
6 .chart {
7 float: left;
8 }

https://gist.github.com/cjhin/8872a492d44694ecf5a883642926f19c 4/9
25/4/2020 D3 Day/Week/Month Line Chart
9
10 .button-area {
11 padding: 40px 0 0 30px;
12 float: left;
13 }
14
15 .app-button {
16 display: block;
17 margin-bottom: 3px;
18 }
19
20 .axis text {
21 font-size: 0.8em;
22 }
23
24 .axis line {
25 stroke: #000;
26 }
27
28 .axis path {
29 display: none;
30 }
31
32 .line {
33 stroke: orange;
34 fill: none;
35 stroke-width: 3px;
36 }
37
38 .overlay {
39 fill: none;
40 pointer-events: all;
41 }
42
43 .tooltip {
44 position: absolute;
45 padding: 10px;
46 font: 12px sans-serif;
47 background: #222;
48 color: #fff;
49 border: 0px;
50 border-radius: 8px;
51 pointer-events: none;
52 opacity: 0.9;
53 visibility: hidden;
54 }
55 </style>
56
57 <script src="//d3js.org/d3.v3.min.js"></script>
58 <script>
59 d3.csv('data.csv', function(day_data) {
60 var formatDate = d3.time.format("%Y-%m-%d");
61 var bisectDate = d3.bisector(function(d) { return formatDate.parse(d['date']); }).left;
62
63 // fix those integers
64 day_data.forEach(function(d, i) {
65 d.value = parseInt(d.value);
66 });
67
68 ///////////////////////
69 // generate weekly data
70 var week_data = create_time_unit_data(parse_for_week);
71
72 function parse_for_week(date) {
73 return formatDate.parse(date).getDay();
74 }
75
76 ///////////////////////
77 // generate monthly data
78 var month_data = create_time_unit_data(parse_for_month);
79
80 function parse_for_month(date) {
81 return formatDate.parse(date).getDate() - 1;

https://gist.github.com/cjhin/8872a492d44694ecf5a883642926f19c 5/9
25/4/2020 D3 Day/Week/Month Line Chart
82 }
83
84 ///////////////////////
85 // helper functions
86 function create_time_unit_data(parse_date) {
87 var new_data = JSON.parse(JSON.stringify(day_data));
88 new_data.forEach(function(d, i) {
89 var offset = parse_date(d.date);
90 if(offset == 0 || i == 0 || i == day_data.length - 1) { // it's a new date_unit, or this is the first or last date in the
91 d['start'] = true;
92 } else {
93 d['start'] = false;
94
95 // add this value to the start of the week
96 var tar_idx = i - offset;
97 if(tar_idx < 0) { tar_idx = 0; }
98 new_data[tar_idx].value += d.value;
99
100 // then nil out
101 d.value = -1;
102 }
103 });
104
105 fill_in_gaps(new_data);
106 return new_data;
107 }
108
109 function fill_in_gaps(data_array) {
110 // go back in and fill in the missing dates
111 data_array.forEach(function(d, i) {
112 if(d.start == false) {
113 var prev_val, next_val, prev_dist, next_dist;
114 for(var idx = i; idx>=0; idx--) {
115 if(data_array[idx].start == true) {
116 prev_val = data_array[idx].value;
117 prev_dist = i - idx;
118 break;
119 }
120 }
121
122 for(var idx = i; idx < data_array.length; idx++) {
123 if(data_array[idx].start == true) {
124 next_val = data_array[idx].value;
125 next_dist = idx - i;
126 break;
127 }
128 }
129
130 d.value = prev_val + ((next_val - prev_val) * (prev_dist/ (prev_dist + next_dist)));
131 }
132 });
133 }
134
135 ///////////////////////
136 // Time unit selection buttons
137 var all_data = [
138 { 'name': 'daily', 'data': day_data},
139 { 'name': 'weekly', 'data': week_data},
140 { 'name': 'monthly', 'data': month_data}
141 ];
142
143 d3.select('.button-area').selectAll('.app-button')
144 .data(all_data)
145 .enter().append('button')
146 .attr('class', 'app-button')
147 .html(function(d) { return d.name; })
148 .on('click', function(d) {
149 curr_data = d.data;
150 updateChart();
151 });
152
153 ///////////////////////
154 // Chart Size Setup

https://gist.github.com/cjhin/8872a492d44694ecf5a883642926f19c 6/9
25/4/2020 D3 Day/Week/Month Line Chart
155 var margin = { top: 40, right: 50, bottom: 20, left: 20 };
156
157 var width = 840 - margin.left - margin.right;
158 var height = 500 - margin.top - margin.bottom;
159
160 var chart = d3.select(".chart")
161 .attr("width", 840)
162 .attr("height", 500)
163 .append("g")
164 .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
165
166 ///////////////////////
167 // Title
168 chart.append("text")
169 .text("Time Series")
170 .attr("text-anchor", "middle")
171 .attr("class", "graph-title")
172 .attr("y", -10)
173 .attr("x", width / 2.0);
174
175 chart.append("text")
176 .attr("text-anchor", "middle")
177 .attr("style", "font-size: 0.9em")
178 .attr("y", height + 50)
179 .attr("x", width / 2.0);
180
181 ///////////////////////
182 // Scales
183 var x = d3.time.scale()
184 .domain(d3.extent(day_data, function(d) { return formatDate.parse(d.date); }))
185 .range([0, width]);
186
187 var y;
188
189 ///////////////////////
190 // Axis
191 var xAxis = d3.svg.axis()
192 .scale(x)
193 .orient("bottom");
194
195 chart.append("g")
196 .attr("class", "x axis")
197 .attr("transform", "translate(0," + height + ")")
198 .call(xAxis);
199
200 ///////////////////////
201 // Tooltips
202 var overlay = chart.append("rect")
203 .attr("class", "overlay")
204 .attr("width", width)
205 .attr("height", height)
206 .on("mouseover", function() { chart.selectAll('.focus').style("display", null); })
207 .on("mouseout", function() { chart.selectAll('.focus').style("display", "none"); })
208
209 var tooltip = d3.select("body").append("div")
210 .attr("class", "tooltip");
211
212 var focus = chart.append("g")
213 .attr("class", "focus")
214 .style("display", "none");
215
216 focus.append("circle")
217 .attr("r", 4.5)
218
219 focus.append("text")
220 .attr("x", 9)
221 .attr("dy", ".35em");
222
223 ///////////////////////
224 // DYNAMIC STUFF GOES HERE
225 // any data things that need to update (yxis, lines, etc)
226 function updateChart() {
227 ///////////////////////

https://gist.github.com/cjhin/8872a492d44694ecf5a883642926f19c 7/9
25/4/2020 D3 Day/Week/Month Line Chart
228 // Y axis changes
229 y = d3.scale.linear()
230 .domain([0, d3.max(curr_data, function(d) { return d.value; })])
231 .range([height, 0]);
232
233 var yAxis = d3.svg.axis()
234 .scale(y)
235 .orient("right");
236
237 // remove any old axis
238 chart.selectAll(".y-axis").remove()
239
240 chart.append("g")
241 .attr("class", "y axis y-axis")
242 .attr("transform", "translate(" + (width + 5) + ",0)")
243 .call(yAxis);
244
245 ///////////////////////
246 // Line changes
247 var lineGenerator = d3.svg.line()
248 .x(function(d) { return x(formatDate.parse(d.date)) })
249 .y(function(d) { return y(d.value) });
250
251 var lines = chart.selectAll(".line")
252 .data([curr_data])
253
254 lines.enter().append("path")
255 .attr("class", "line")
256 .attr("d", lineGenerator);
257
258 lines.transition()
259 .duration(1000)
260 .attr("d", lineGenerator);
261
262 overlay.on("mousemove", mousemove);
263 }
264
265 function mousemove() {
266 var x0 = x.invert(d3.mouse(this)[0]),
267 i = bisectDate(curr_data, x0, 1),
268 d0 = curr_data[i - 1],
269 d1 = curr_data[i];
270
271 // search for dates where "start" == true, in other words, only valid dates for the dataset
272 // start of week, start of month
273 var i0 = i - 1;
274 while(d0.start == false && i0 > 0) {
275 i0 -= 1;
276 d0 = curr_data[i0];
277 }
278 var i1 = i;
279 while(d1.start == false && i1 < curr_data.length - 1) {
280 i1 += 1;
281 d1 = curr_data[i1];
282 }
283
284 var dIdx = x0 - d0.date > d1.date - x0 ? i1 : i0;
285
286 var tar_date = curr_data[dIdx]['date'];
287 var tooltip_string = tar_date;
288
289 var tar_value = curr_data[dIdx].value;
290 focus.attr("transform", "translate(" + x(formatDate.parse(tar_date)) + ","+y(tar_value)+ ")");
291 tooltip_string += "<br>" + tar_value;
292
293 tooltip.html(tooltip_string)
294 .style("visibility", "visible")
295 .style("top", d3.mouse(this)[1] - (tooltip[0][0].clientHeight - 30) + "px")
296 .style("left", d3.mouse(this)[0] - (tooltip[0][0].clientWidth / 2.0) + "px");
297 }
298
299 // default
300 curr_data = day_data;

https://gist.github.com/cjhin/8872a492d44694ecf5a883642926f19c 8/9
25/4/2020 D3 Day/Week/Month Line Chart
301 updateChart();
302 });
303 </script>
304
305 <body>
306 <svg class="chart"></svg>
307 <div class='button-area'>
308 </div>
309 </body>

thumbnail.png

https://gist.github.com/cjhin/8872a492d44694ecf5a883642926f19c 9/9

You might also like