Make connectSeparatedPoints a per-series option. Add automated test and visual test.
[dygraphs.git] / datahandler / datahandler.js
CommitLineData
a49c164a
DE
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 */
a49c164a
DE
41/*global Dygraph:false */
42/*global DygraphLayout:false */
3ea41d86
DV
43
44(function() {
45
a49c164a
DE
46"use strict";
47
48/**
49 * A collection of functions to create and retrieve data handlers.
3ea41d86 50 * @type {Object.<!Dygraph.DataHandler>}
a49c164a
DE
51 */
52Dygraph.DataHandlers = {};
53
54/**
a49c164a
DE
55 *
56 * The data handler is responsible for all data specific operations. All of the
57 * series data it receives and returns is always in the unified data format.
58 * Initially the unified data is created by the extractSeries method
a49c164a
DE
59 */
60Dygraph.DataHandler = function () {
61 /**
62 * Constructor for all data handlers.
63 * @constructor
64 */
65 var handler = function() {
66 return this;
67 };
68
69 /**
70 * X-value array index constant for unified data samples.
71 * @const
72 * @type {number}
73 */
74 handler.X = 0;
75
76 /**
77 * Y-value array index constant for unified data samples.
78 * @const
79 * @type {number}
80 */
81 handler.Y = 1;
82
83 /**
84 * Extras-value array index constant for unified data samples.
85 * @const
86 * @type {number}
87 */
88 handler.EXTRAS = 2;
89
90 /**
91 * Extracts one series from the raw data (a 2D array) into an array of the
92 * unified data format.
93 * This is where undesirable points (i.e. negative values on log scales and
94 * missing values through which we wish to connect lines) are dropped.
95 * TODO(danvk): the "missing values" bit above doesn't seem right.
96 *
97 * @param rawData {!Array.<Array>} The raw data passed into dygraphs where
98 * rawData[i] = [x,ySeries1,...,ySeriesN].
99 * @param seriesIndex {!number} Index of the series to extract. All other series should
100 * be ignored.
101 * @param options {!DygraphOptions} Dygraph options.
102 * @returns {Array.<[!number,?number,?]>} The series in the unified data format
103 * where series[i] = [x,y,{extras}].
104 * @public
105 */
106 handler.prototype.extractSeries = function(rawData, seriesIndex, options) {
107 };
108
109 /**
110 * Converts a series to a Point array.
111 *
112 * @param {!Array.<[!number,?number,?]>} series The series in the unified
113 * data format where series[i] = [x,y,{extras}].
114 * @param {!string} setName Name of the series.
115 * @param {!number} boundaryIdStart Index offset of the first point, equal to the
116 * number of skipped points left of the date window minimum (if any).
117 * @return {!Array.<Dygraph.PointType>} List of points for this series.
118 * @public
119 */
120 handler.prototype.seriesToPoints = function(series, setName, boundaryIdStart) {
121 // TODO(bhs): these loops are a hot-spot for high-point-count charts. In
122 // fact,
123 // on chrome+linux, they are 6 times more expensive than iterating through
124 // the
125 // points and drawing the lines. The brunt of the cost comes from allocating
126 // the |point| structures.
127 var points = [];
128 for ( var i = 0; i < series.length; ++i) {
129 var item = series[i];
130 var yraw = item[1];
131 var yval = yraw === null ? null : DygraphLayout.parseFloat_(yraw);
132 var point = {
133 x : NaN,
134 y : NaN,
135 xval : DygraphLayout.parseFloat_(item[0]),
136 yval : yval,
137 name : setName, // TODO(danvk): is this really necessary?
138 idx : i + boundaryIdStart
139 };
140 points.push(point);
141 }
142 handler.prototype.onPointsCreated_(series, points);
143 return points;
144 };
145
146 /**
147 * Callback called for each series after the series points have been generated
148 * which will later be used by the plotters to draw the graph.
149 * Here data may be added to the seriesPoints which is needed by the plotters.
150 * The indexes of series and points are in sync meaning the original data
151 * sample for series[i] is points[i].
152 *
153 * @param {!Array.<[!number,?number,?]>} series The series in the unified
154 * data format where series[i] = [x,y,{extras}].
155 * @param {!Array.<Dygraph.PointType>} points The corresponding points passed
156 * to the plotter.
157 * @private
158 */
159 handler.prototype.onPointsCreated_ = function(series, points) {
160 };
161
162 /**
163 * Calculates the rolling average of a data set.
164 *
165 * @param {!Array.<[!number,?number,?]>} series The series in the unified
166 * data format where series[i] = [x,y,{extras}].
167 * @param {!number} rollPeriod The number of points over which to average the data
168 * @param {!DygraphOptions} options The dygraph options.
169 * @return the rolled series.
170 * @public
171 */
172 handler.prototype.rollingAverage = function(series, rollPeriod, options) {
173 };
174
175 /**
176 * Computes the range of the data series (including confidence intervals).
177 *
178 * @param {!Array.<[!number,?number,?]>} series The series in the unified
179 * data format where series[i] = [x,y,{extras}].
180 * @param {!Array.<number>} dateWindow The x-value range to display with
181 * the format: [min,max].
182 * @param {!DygraphOptions} options The dygraph options.
183 * @return {Array.<number>} The low and high extremes of the series in the given window with
184 * the format: [low, high].
185 * @public
186 */
187 handler.prototype.getExtremeYValues = function(series, dateWindow, options) {
188 };
189
190 /**
191 * Callback called for each series after the layouting data has been
192 * calculated before the series is drawn. Here normalized positioning data
193 * should be calculated for the extras of each point.
194 *
195 * @param {!Array.<Dygraph.PointType>} points The points passed to
196 * the plotter.
197 * @param {!Object} axis The axis on which the series will be plotted.
198 * @param {!boolean} logscale Weather or not to use a logscale.
199 * @public
200 */
201 handler.prototype.onLineEvaluated = function(points, axis, logscale) {
202 };
203
204 /**
205 * Helper method that computes the y value of a line defined by the points p1
206 * and p2 and a given x value.
207 *
208 * @param {!Array.<number>} p1 left point ([x,y]).
209 * @param {!Array.<number>} p2 right point ([x,y]).
210 * @param {!number} xValue The x value to compute the y-intersection for.
211 * @return {number} corresponding y value to x on the line defined by p1 and p2.
212 * @private
213 */
214 handler.prototype.computeYInterpolation_ = function(p1, p2, xValue) {
215 var deltaY = p2[1] - p1[1];
216 var deltaX = p2[0] - p1[0];
217 var gradient = deltaY / deltaX;
218 var growth = (xValue - p1[0]) * gradient;
219 return p1[1] + growth;
220 };
221
222 /**
223 * Helper method that returns the first and the last index of the given series
224 * that lie inside the given dateWindow.
225 *
226 * @param {!Array.<[!number,?number,?]>} series The series in the unified
227 * data format where series[i] = [x,y,{extras}].
228 * @param {!Array.<number>} dateWindow The x-value range to display with
229 * the format: [min,max].
230 * @return {!Array.<[!number,?number,?]>} The samples of the series that
231 * are in the given date window.
232 * @private
233 */
234 handler.prototype.getIndexesInWindow_ = function(series, dateWindow) {
235 var firstIdx = 0, lastIdx = series.length - 1;
236 if (dateWindow) {
237 var idx = 0;
238 var low = dateWindow[0];
239 var high = dateWindow[1];
240
241 // Start from each side of the array to minimize the performance
242 // needed.
243 while (idx < series.length - 1 && series[idx][0] < low) {
244 firstIdx++;
245 idx++;
246 }
247 idx = series.length - 1;
248 while (idx > 0 && series[idx][0] > high) {
249 lastIdx--;
250 idx--;
251 }
252 }
253 if (firstIdx <= lastIdx) {
254 return [ firstIdx, lastIdx ];
255 } else {
256 return [ 0, series.length - 1 ];
257 }
258 };
259
260 return handler;
261};
3ea41d86
DV
262
263})();