3 * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com)
4 * MIT-licensed (http://opensource.org/licenses/MIT)
8 * @fileoverview This file contains the managment of data handlers
9 * @author David Eberlein (david.eberlein@ch.sauter-bc.com)
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
23 * The unified data format returend by each handler is defined as so:
24 * series[n][point] = [x,y,(extras)]
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.
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
36 * In practice this might look something like this:
38 * errorBar / customBar: [x, yVal, [yTopVariance, yBottomVariance] ]
41 /*global Dygraph:false */
42 /*global DygraphLayout:false */
46 * The data handler is responsible for all data specific operations. All of the
47 * series data it receives and returns is always in the unified data format.
48 * Initially the unified data is created by the extractSeries method
51 Dygraph
.DataHandler
= function () {
55 * A collection of functions to create and retrieve data handlers.
56 * @type {Object.<!Dygraph.DataHandler>}
58 Dygraph
.DataHandlers
= {};
64 var handler
= Dygraph
.DataHandler
;
67 * X-value array index constant for unified data samples.
74 * Y-value array index constant for unified data samples.
81 * Extras-value array index constant for unified data samples.
88 * Extracts one series from the raw data (a 2D array) into an array of the
89 * unified data format.
90 * This is where undesirable points (i.e. negative values on log scales and
91 * missing values through which we wish to connect lines) are dropped.
92 * TODO(danvk): the "missing values" bit above doesn't seem right.
94 * @param {!Array.<Array>} rawData The raw data passed into dygraphs where
95 * rawData[i] = [x,ySeries1,...,ySeriesN].
96 * @param {!number} seriesIndex Index of the series to extract. All other
97 * series should be ignored.
98 * @param {!DygraphOptions} options Dygraph options.
99 * @return {Array.<[!number,?number,?]>} The series in the unified data format
100 * where series[i] = [x,y,{extras}].
102 handler
.prototype.extractSeries
= function(rawData
, seriesIndex
, options
) {
106 * Converts a series to a Point array. The resulting point array must be
107 * returned in increasing order of idx property.
109 * @param {!Array.<[!number,?number,?]>} series The series in the unified
110 * data format where series[i] = [x,y,{extras}].
111 * @param {!string} setName Name of the series.
112 * @param {!number} boundaryIdStart Index offset of the first point, equal to the
113 * number of skipped points left of the date window minimum (if any).
114 * @return {!Array.<Dygraph.PointType>} List of points for this series.
116 handler
.prototype.seriesToPoints
= function(series
, setName
, boundaryIdStart
) {
117 // TODO(bhs): these loops are a hot-spot for high-point-count charts. In
119 // on chrome+linux, they are 6 times more expensive than iterating through
121 // points and drawing the lines. The brunt of the cost comes from allocating
122 // the |point| structures.
124 for ( var i
= 0; i
< series
.length
; ++i
) {
125 var item
= series
[i
];
127 var yval
= yraw
=== null ? null : handler
.parseFloat(yraw
);
131 xval
: handler
.parseFloat(item
[0]),
133 name
: setName
, // TODO(danvk): is this really necessary?
134 idx
: i
+ boundaryIdStart
138 this.onPointsCreated_(series
, points
);
143 * Callback called for each series after the series points have been generated
144 * which will later be used by the plotters to draw the graph.
145 * Here data may be added to the seriesPoints which is needed by the plotters.
146 * The indexes of series and points are in sync meaning the original data
147 * sample for series[i] is points[i].
149 * @param {!Array.<[!number,?number,?]>} series The series in the unified
150 * data format where series[i] = [x,y,{extras}].
151 * @param {!Array.<Dygraph.PointType>} points The corresponding points passed
155 handler
.prototype.onPointsCreated_
= function(series
, points
) {
159 * Calculates the rolling average of a data set.
161 * @param {!Array.<[!number,?number,?]>} series The series in the unified
162 * data format where series[i] = [x,y,{extras}].
163 * @param {!number} rollPeriod The number of points over which to average the data
164 * @param {!DygraphOptions} options The dygraph options.
165 * @return {!Array.<[!number,?number,?]>} the rolled series.
167 handler
.prototype.rollingAverage
= function(series
, rollPeriod
, options
) {
171 * Computes the range of the data series (including confidence intervals).
173 * @param {!Array.<[!number,?number,?]>} series The series in the unified
174 * data format where series[i] = [x, y, {extras}].
175 * @param {!Array.<number>} dateWindow The x-value range to display with
176 * the format: [min, max].
177 * @param {!DygraphOptions} options The dygraph options.
178 * @return {Array.<number>} The low and high extremes of the series in the
179 * given window with the format: [low, high].
181 handler
.prototype.getExtremeYValues
= function(series
, dateWindow
, options
) {
185 * Callback called for each series after the layouting data has been
186 * calculated before the series is drawn. Here normalized positioning data
187 * should be calculated for the extras of each point.
189 * @param {!Array.<Dygraph.PointType>} points The points passed to
191 * @param {!Object} axis The axis on which the series will be plotted.
192 * @param {!boolean} logscale Weather or not to use a logscale.
194 handler
.prototype.onLineEvaluated
= function(points
, axis
, logscale
) {
198 * Helper method that computes the y value of a line defined by the points p1
199 * and p2 and a given x value.
201 * @param {!Array.<number>} p1 left point ([x,y]).
202 * @param {!Array.<number>} p2 right point ([x,y]).
203 * @param {!number} xValue The x value to compute the y-intersection for.
204 * @return {number} corresponding y value to x on the line defined by p1 and p2.
207 handler
.prototype.computeYInterpolation_
= function(p1
, p2
, xValue
) {
208 var deltaY
= p2
[1] - p1
[1];
209 var deltaX
= p2
[0] - p1
[0];
210 var gradient
= deltaY
/ deltaX
;
211 var growth
= (xValue
- p1
[0]) * gradient
;
212 return p1
[1] + growth
;
216 * Helper method that returns the first and the last index of the given series
217 * that lie inside the given dateWindow.
219 * @param {!Array.<[!number,?number,?]>} series The series in the unified
220 * data format where series[i] = [x,y,{extras}].
221 * @param {!Array.<number>} dateWindow The x-value range to display with
222 * the format: [min,max].
223 * @return {!Array.<[!number,?number,?]>} The samples of the series that
224 * are in the given date window.
227 handler
.prototype.getIndexesInWindow_
= function(series
, dateWindow
) {
228 var firstIdx
= 0, lastIdx
= series
.length
- 1;
231 var low
= dateWindow
[0];
232 var high
= dateWindow
[1];
234 // Start from each side of the array to minimize the performance
236 while (idx
< series
.length
- 1 && series
[idx
][0] < low
) {
240 idx
= series
.length
- 1;
241 while (idx
> 0 && series
[idx
][0] > high
) {
246 if (firstIdx
<= lastIdx
) {
247 return [ firstIdx
, lastIdx
];
249 return [ 0, series
.length
- 1 ];
254 * Optimized replacement for parseFloat, which was way too slow when almost
255 * all values were type number, with few edge cases, none of which were strings.
256 * @param {?number} val
260 handler
.parseFloat
= function(val
) {
261 // parseFloat(null) is NaN
266 // Assume it's a number or NaN. If it's something else, I'll be shocked.