otsdaq_utilities  v2_05_02_indev
timeseries.js
1 /* TIMESERIES - A simple D3.js timeseries.
2 * call timeseries(<classd>, <data>, <enableBrush>) with the following parameters
3 * classd - the class name of your container div for the timeseries to attach to
4 * enableBrush - whether to enable the brush
5 */
6 (function() {
7 
8  var timeseries = function(spaced, data, enableBrush) {
9  classd = spaced.replace(new RegExp(" "), ".");
10  render(classd, spaced, data, enableBrush);
11  }
12 
13  // ---------------------------------------------------------------------------------------------
14  // ---------------------------------- Time Manipulation ----------------------------------------
15  // ---------------------------------------------------------------------------------------------
16 
17  function lessThanDay(d) {
18  return (d === "hours" || d === "minutes" || d === "seconds") ? true : false;
19  }
20 
21  function getDate(d) {
22  var date = moment(d);
23  date.hour(1);
24  date.minute(0);
25  date.second(0);
26  return date.valueOf();
27  }
28 
29  function getTime(d) {
30  var date = moment(d);
31  date.date(1);
32  date.month(0);
33  date.year(2012);
34  return date.valueOf();
35  }
36 
37  /*
38  Given a list of time stamps, compute the minimum and maxium dates. Return a padded
39  version of the min and max dates based on the temporal distance between them.
40  */
41  function timeRangePad(dates) {
42  var minDate, maxDate, pad;
43  if (dates.length > 1) {
44  minDate = moment(_.min(dates));
45  maxDate = moment(_.max(dates));
46  pad = getDatePadding(minDate, maxDate);
47  minDate.subtract(1, pad);
48  maxDate.add(1, pad);
49  } else {
50  minDate = moment(dates[0]).subtract(1, 'hour');
51  maxDate = moment(dates[0]).add(1, 'hour');
52  }
53  return {
54  'minDate': minDate,
55  'maxDate': maxDate,
56  'pad': pad
57  };
58  };
59 
60  function getDatePadding(minDate, maxDate) {
61  if (maxDate.diff(minDate, 'years') > 0)
62  return 'months';
63  else if (maxDate.diff(minDate, 'months') > 0)
64  return 'days';
65  else if (maxDate.diff(minDate, 'days') > 0)
66  return 'days';
67  else if (maxDate.diff(minDate, 'hours') > 0)
68  return 'hours';
69  else if (maxDate.diff(minDate, 'minutes') > 0)
70  return 'minutes';
71  else
72  return 'seconds';
73  }
74 
75  // ---------------------------------------------------------------------------------------------
76  // ------------------------------------- Rendering ---------------------------------------------
77  // ---------------------------------------------------------------------------------------------
78 
79  function render(classd, spaced, data, enableBrush) {
80 
81  var padding = timeRangePad(_.pluck(data, 'value'));
82 
83  var margin = {
84  top: 10,
85  right: 25,
86  bottom: 15,
87  left: 35
88  }
89  var width = window.innerWidth - 150;
90  var height = (lessThanDay(padding.pad)) ? (100 - margin.top - margin.bottom) : (300 - margin.top - margin.bottom);
91 
92  var x = d3.time.scale().range([0 + margin.right, width - margin.left]),
93  y = d3.time.scale()
94  .range([margin.top, height - margin.bottom - margin.top]);
95 
96  var ticks = width > 800 ? 8 : 4;
97 
98  x.domain(d3.extent([padding.minDate, padding.maxDate]));
99 
100  var xFormat, yFormat;
101  if (lessThanDay(padding.pad)) {
102  xFormat = "%H:%M";
103  yFormat = "%m/%d/%y";
104  y.domain(d3.extent([padding.minDate]));
105  } else {
106  xFormat = "%m/%d/%y";
107  yFormat = "%H:%M";
108  var start = new Date(2012, 0, 1, 0, 0, 0, 0).getTime();
109  var stop = new Date(2012, 0, 1, 23, 59, 59, 59).getTime();
110  y.domain(d3.extent([start, stop]));
111  }
112 
113  var xAxis = d3.svg.axis().scale(x).orient("bottom")
114  .ticks(ticks)
115  .tickSize(-height, 0)
116  .tickFormat(d3.time.format(xFormat));
117 
118  var yAxis = d3.svg.axis().scale(y).orient("left")
119  .ticks(5)
120  .tickSize(-width + margin.right, margin.left)
121  .tickFormat(d3.time.format(yFormat));
122 
123  var svg = d3.select("." + classd).append("svg")
124  .attr("width", width + margin.left + margin.right)
125  .attr("height", height + margin.top + margin.bottom);
126 
127  var context = svg.append("g")
128  .attr("class", "context")
129  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
130 
131  context.append("g")
132  .attr("class", "x axis")
133  .attr("transform", "translate(" + margin.left + "," + (margin.top + (height - margin.bottom)) + ")")
134  .call(xAxis);
135 
136  context.append("g")
137  .attr("class", "y axis")
138  .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
139  .call(yAxis);
140 
141  var circles = context.append("g")
142  .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
143 
144  circles.selectAll(".circ")
145  .data(data)
146  .enter().append("circle")
147  .attr("class", "circ")
148  .attr("cx", function(d) {
149  return (lessThanDay(padding.pad)) ? x(d.value) : x(getDate(d.value));
150  })
151  .attr("cy", function(d, i) {
152  return (lessThanDay(padding.pad)) ? y(getDate(d.value)) : y(getTime(d.value));
153  })
154  .attr("r", 9)
155  .on("click", function(d) {
156  console.log(new Date(d.value));
157  })
158 
159  // ----------------------------------------- Brush ---------------------------------------------
160 
161  if (enableBrush) {
162  var brush = d3.svg.brush()
163  .x(x)
164  .on("brush", _.throttle(brushed, 200));
165 
166  circles.append("g")
167  .attr("class", "brush")
168  .call(brush)
169  .selectAll("rect")
170  .attr("y", -6)
171  .attr("height", height - margin.bottom);
172 
173  var brushEl = '<div class="brush-control"><div class="brush-info"><i>Click and drag on the timeseries to create a brush.</i></div><button class="clear-brush">Clear brush</button></div>';
174  window.document.getElementsByClassName(spaced)[0].insertAdjacentHTML('beforeend', brushEl);
175 
176  function brushed() {
177  if (!brush.empty()) {
178  d3.select('.clear-brush').style("display", "inline-block");
179  d3.select('.brush-info')[0][0].innerText = brush.extent();
180  }
181  }
182 
183  d3.select('.clear-brush').on("click", function(d) {
184  if (!brush.empty()) {
185  d3.selectAll("g.brush").call(brush.clear());
186  d3.select('.brush-info')[0][0].innerText = "";
187  d3.select('.clear-brush').style("display", "none");
188  }
189  })
190 
191  timeseries.getBrushExtent = function() {
192  if (brush)
193  return brush.extent();
194  }
195  }
196  }
197 
198  /* Use this function, in conjunction to setting a time element to 'selected', to highlight the
199  data point on the timeseries. */
200  function redraw() {
201  d3.selectAll(".circ")
202  .transition(10)
203  .style("opacity", function(d) {
204  return d.selected ? 1 : 0.6;
205  })
206  .attr("r", function(d) {
207  return d.selected ? 15 : 7;
208  });
209  }
210 
211  if (typeof define === "function" && define.amd) define(timeseries);
212  else if (typeof module === "object" && module.exports) module.exports = timeseries;
213  this.timeseries = timeseries;
214 
215 })();
216