Datahandler and Unified Data Format
[dygraphs.git] / datahandler / datahandler.js
1 /**
2 * @license
3 * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com)
4 * MIT-licensed (http://opensource.org/licenses/MIT)
5 */
6
7 /**
8 * @fileoverview This file contains the managment of data handlers
9 * @author David Eberlein (david.eberlein@ch.sauter-bc.com)
10 *
11 * The idea is to define a common, generic data format that works for all data
12 * structures supported by dygraphs. To make this possible, the DataHandler
13 * interface is introduced. This makes it possible, that dygraph itself can work
14 * with the same logic for every data type independent of the actual format and
15 * the DataHandler takes care of the data format specific jobs.
16 * DataHandlers are implemented for all data types supported by Dygraphs and
17 * return Dygraphs compliant formats.
18 * By default the correct DataHandler is chosen based on the options set.
19 * Optionally the user may use his own DataHandler (similar to the plugin
20 * system).
21 *
22 *
23 * The unified data format returend by each handler is defined as so:
24 * series[n][point] = [x,y,(extras)]
25 *
26 * This format contains the common basis that is needed to draw a simple line
27 * series extended by optional extras for more complex graphing types. It
28 * contains a primitive x value as first array entry, a primitive y value as
29 * second array entry and an optional extras object for additional data needed.
30 *
31 * x must always be a number.
32 * y must always be a number, NaN of type number or null.
33 * extras is optional and must be interpreted by the DataHandler. It may be of
34 * any type.
35 *
36 * In practice this might look something like this:
37 * default: [x, yVal]
38 * errorBar / customBar: [x, yVal, [yTopVariance, yBottomVariance] ]
39 *
40 */
41 /*jshint globalstrict: true */
42 /*global Dygraph:false */
43 /*global DygraphLayout:false */
44 "use strict";
45
46 /**
47 * A collection of functions to create and retrieve data handlers.
48 */
49 Dygraph.DataHandlers = {};
50
51 /**
52 * All registered data handlers are stored here.
53 *
54 * @private
55 */
56 Dygraph.DataHandlers.handlers_ = {};
57
58 /**
59 * @param name {!string} The name the data handler should be registered to.
60 * Registers a data handler by the given name and makes it publicly
61 * accessible.
62 * @param handler {!Dygraph.DataHandler} DataHandler implementation which must be an
63 * instance of Dygraph.DataHandler.
64 * @public
65 */
66 Dygraph.DataHandlers.registerHandler = function(name, handler) {
67 if (!handler instanceof Dygraph.DataHandler) {
68 throw ("the handler must be a prototype of Dygraph.DataHandler");
69 }
70 Dygraph.DataHandlers.handlers_[name] = handler;
71 };
72
73 /**
74 * Returns the data handler registered to the given name.
75 * Note this is the data handler constructor method.
76 *
77 * @param name {!string} The name, the handler was registered to.
78 * @returns {Dygraph.DataHandler} The data handler constructor.
79 * @public
80 */
81 Dygraph.DataHandlers.getHandler = function(name) {
82 return Dygraph.DataHandlers.handlers_[name];
83 };
84
85 /**
86 * Returns the cunstructed data handler registered to the given name.
87 *
88 * @param name {!string} The name, the handler was registered to.
89 * @returns {Dygraph.DataHandler} A constructed instance of the data handler.
90 * @public
91 */
92 Dygraph.DataHandlers.createHandler = function(name) {
93 return new Dygraph.DataHandlers.handlers_[name]();
94 };
95
96 /**
97 *
98 * The data handler is responsible for all data specific operations. All of the
99 * series data it receives and returns is always in the unified data format.
100 * Initially the unified data is created by the extractSeries method
101 *
102 * @class
103 */
104 Dygraph.DataHandler = function () {
105 /**
106 * Constructor for all data handlers.
107 * @constructor
108 */
109 var handler = function() {
110 return this;
111 };
112
113 /**
114 * X-value array index constant for unified data samples.
115 * @const
116 * @type {number}
117 */
118 handler.X = 0;
119
120 /**
121 * Y-value array index constant for unified data samples.
122 * @const
123 * @type {number}
124 */
125 handler.Y = 1;
126
127 /**
128 * Extras-value array index constant for unified data samples.
129 * @const
130 * @type {number}
131 */
132 handler.EXTRAS = 2;
133
134 /**
135 * Extracts one series from the raw data (a 2D array) into an array of the
136 * unified data format.
137 * This is where undesirable points (i.e. negative values on log scales and
138 * missing values through which we wish to connect lines) are dropped.
139 * TODO(danvk): the "missing values" bit above doesn't seem right.
140 *
141 * @param rawData {!Array.<Array>} The raw data passed into dygraphs where
142 * rawData[i] = [x,ySeries1,...,ySeriesN].
143 * @param seriesIndex {!number} Index of the series to extract. All other series should
144 * be ignored.
145 * @param options {!DygraphOptions} Dygraph options.
146 * @returns {Array.<[!number,?number,?]>} The series in the unified data format
147 * where series[i] = [x,y,{extras}].
148 * @public
149 */
150 handler.prototype.extractSeries = function(rawData, seriesIndex, options) {
151 };
152
153 /**
154 * Converts a series to a Point array.
155 *
156 * @param {!Array.<[!number,?number,?]>} series The series in the unified
157 * data format where series[i] = [x,y,{extras}].
158 * @param {!string} setName Name of the series.
159 * @param {!number} boundaryIdStart Index offset of the first point, equal to the
160 * number of skipped points left of the date window minimum (if any).
161 * @return {!Array.<Dygraph.PointType>} List of points for this series.
162 * @public
163 */
164 handler.prototype.seriesToPoints = function(series, setName, boundaryIdStart) {
165 // TODO(bhs): these loops are a hot-spot for high-point-count charts. In
166 // fact,
167 // on chrome+linux, they are 6 times more expensive than iterating through
168 // the
169 // points and drawing the lines. The brunt of the cost comes from allocating
170 // the |point| structures.
171 var points = [];
172 for ( var i = 0; i < series.length; ++i) {
173 var item = series[i];
174 var yraw = item[1];
175 var yval = yraw === null ? null : DygraphLayout.parseFloat_(yraw);
176 var point = {
177 x : NaN,
178 y : NaN,
179 xval : DygraphLayout.parseFloat_(item[0]),
180 yval : yval,
181 name : setName, // TODO(danvk): is this really necessary?
182 idx : i + boundaryIdStart
183 };
184 points.push(point);
185 }
186 handler.prototype.onPointsCreated_(series, points);
187 return points;
188 };
189
190 /**
191 * Callback called for each series after the series points have been generated
192 * which will later be used by the plotters to draw the graph.
193 * Here data may be added to the seriesPoints which is needed by the plotters.
194 * The indexes of series and points are in sync meaning the original data
195 * sample for series[i] is points[i].
196 *
197 * @param {!Array.<[!number,?number,?]>} series The series in the unified
198 * data format where series[i] = [x,y,{extras}].
199 * @param {!Array.<Dygraph.PointType>} points The corresponding points passed
200 * to the plotter.
201 * @private
202 */
203 handler.prototype.onPointsCreated_ = function(series, points) {
204 };
205
206 /**
207 * Calculates the rolling average of a data set.
208 *
209 * @param {!Array.<[!number,?number,?]>} series The series in the unified
210 * data format where series[i] = [x,y,{extras}].
211 * @param {!number} rollPeriod The number of points over which to average the data
212 * @param {!DygraphOptions} options The dygraph options.
213 * @return the rolled series.
214 * @public
215 */
216 handler.prototype.rollingAverage = function(series, rollPeriod, options) {
217 };
218
219 /**
220 * Computes the range of the data series (including confidence intervals).
221 *
222 * @param {!Array.<[!number,?number,?]>} series The series in the unified
223 * data format where series[i] = [x,y,{extras}].
224 * @param {!Array.<number>} dateWindow The x-value range to display with
225 * the format: [min,max].
226 * @param {!DygraphOptions} options The dygraph options.
227 * @return {Array.<number>} The low and high extremes of the series in the given window with
228 * the format: [low, high].
229 * @public
230 */
231 handler.prototype.getExtremeYValues = function(series, dateWindow, options) {
232 };
233
234 /**
235 * Callback called for each series after the layouting data has been
236 * calculated before the series is drawn. Here normalized positioning data
237 * should be calculated for the extras of each point.
238 *
239 * @param {!Array.<Dygraph.PointType>} points The points passed to
240 * the plotter.
241 * @param {!Object} axis The axis on which the series will be plotted.
242 * @param {!boolean} logscale Weather or not to use a logscale.
243 * @public
244 */
245 handler.prototype.onLineEvaluated = function(points, axis, logscale) {
246 };
247
248 /**
249 * Helper method that computes the y value of a line defined by the points p1
250 * and p2 and a given x value.
251 *
252 * @param {!Array.<number>} p1 left point ([x,y]).
253 * @param {!Array.<number>} p2 right point ([x,y]).
254 * @param {!number} xValue The x value to compute the y-intersection for.
255 * @return {number} corresponding y value to x on the line defined by p1 and p2.
256 * @private
257 */
258 handler.prototype.computeYInterpolation_ = function(p1, p2, xValue) {
259 var deltaY = p2[1] - p1[1];
260 var deltaX = p2[0] - p1[0];
261 var gradient = deltaY / deltaX;
262 var growth = (xValue - p1[0]) * gradient;
263 return p1[1] + growth;
264 };
265
266 /**
267 * Helper method that returns the first and the last index of the given series
268 * that lie inside the given dateWindow.
269 *
270 * @param {!Array.<[!number,?number,?]>} series The series in the unified
271 * data format where series[i] = [x,y,{extras}].
272 * @param {!Array.<number>} dateWindow The x-value range to display with
273 * the format: [min,max].
274 * @return {!Array.<[!number,?number,?]>} The samples of the series that
275 * are in the given date window.
276 * @private
277 */
278 handler.prototype.getIndexesInWindow_ = function(series, dateWindow) {
279 var firstIdx = 0, lastIdx = series.length - 1;
280 if (dateWindow) {
281 var idx = 0;
282 var low = dateWindow[0];
283 var high = dateWindow[1];
284
285 // Start from each side of the array to minimize the performance
286 // needed.
287 while (idx < series.length - 1 && series[idx][0] < low) {
288 firstIdx++;
289 idx++;
290 }
291 idx = series.length - 1;
292 while (idx > 0 && series[idx][0] > high) {
293 lastIdx--;
294 idx--;
295 }
296 }
297 if (firstIdx <= lastIdx) {
298 return [ firstIdx, lastIdx ];
299 } else {
300 return [ 0, series.length - 1 ];
301 }
302 };
303
304 return handler;
305 };