1 // Copyright 2006 Dan Vanderkam (danvdk@gmail.com)
2 // All Rights Reserved.
5 * @fileoverview Creates an interactive, zoomable graph based on a CSV file or
6 * string. Dygraph can handle multiple series with or without error bars. The
7 * date/value ranges will be automatically set. Dygraph uses the
8 * <canvas> tag, so it only works in FF1.5+.
9 * @author danvdk@gmail.com (Dan Vanderkam)
12 <div id="graphdiv" style="width:800px; height:500px;"></div>
13 <script type="text/javascript">
14 new Dygraph(document.getElementById("graphdiv"),
15 "datafile.csv", // CSV file with headers
19 The CSV file is of the form
21 Date,SeriesA,SeriesB,SeriesC
25 If the 'errorBars' option is set in the constructor, the input should be of
27 Date,SeriesA,SeriesB,...
28 YYYYMMDD,A1,sigmaA1,B1,sigmaB1,...
29 YYYYMMDD,A2,sigmaA2,B2,sigmaB2,...
31 If the 'fractions' option is set, the input should be of the form:
33 Date,SeriesA,SeriesB,...
34 YYYYMMDD,A1/B1,A2/B2,...
35 YYYYMMDD,A1/B1,A2/B2,...
37 And error bars will be calculated automatically using a binomial distribution.
39 For further documentation and examples, see http://dygraphs.com/
44 * Creates an interactive, zoomable chart.
47 * @param {div | String} div A div or the id of a div into which to construct
49 * @param {String | Function} file A file containing CSV data or a function
50 * that returns this data. The most basic expected format for each line is
51 * "YYYY/MM/DD,val1,val2,...". For more information, see
52 * http://dygraphs.com/data.html.
53 * @param {Object} attrs Various other attributes, e.g. errorBars determines
54 * whether the input data contains error ranges. For a complete list of
55 * options, see http://dygraphs.com/options.html.
57 Dygraph
= function(div
, data
, opts
) {
58 if (arguments
.length
> 0) {
59 if (arguments
.length
== 4) {
60 // Old versions of dygraphs took in the series labels as a constructor
61 // parameter. This doesn't make sense anymore, but it's easy to continue
62 // to support this usage.
63 this.warn("Using deprecated four-argument dygraph constructor");
64 this.__old_init__(div
, data
, arguments
[2], arguments
[3]);
66 this.__init__(div
, data
, opts
);
71 Dygraph
.NAME
= "Dygraph";
72 Dygraph
.VERSION
= "1.2";
73 Dygraph
.__repr__
= function() {
74 return "[" + this.NAME
+ " " + this.VERSION
+ "]";
78 * Returns information about the Dygraph class.
80 Dygraph
.toString
= function() {
81 return this.__repr__();
84 // Various default values
85 Dygraph
.DEFAULT_ROLL_PERIOD
= 1;
86 Dygraph
.DEFAULT_WIDTH
= 480;
87 Dygraph
.DEFAULT_HEIGHT
= 320;
88 Dygraph
.AXIS_LINE_WIDTH
= 0.3;
90 Dygraph
.LOG_SCALE
= 10;
91 Dygraph
.LN_TEN
= Math
.log(Dygraph
.LOG_SCALE
);
93 Dygraph
.log10
= function(x
) {
94 return Math
.log(x
) / Dygraph
.LN_TEN
;
97 // Default attribute values.
98 Dygraph
.DEFAULT_ATTRS
= {
99 highlightCircleSize
: 3,
105 // TODO(danvk): move defaults from createStatusMessage_ here.
107 labelsSeparateLines
: false,
108 labelsShowZeroValues
: true,
111 showLabelsOnHighlight
: true,
113 yValueFormatter
: function(a
,b
) { return Dygraph
.numberFormatter(a
,b
); },
114 digitsAfterDecimal
: 2,
121 axisLabelFontSize
: 14,
124 xAxisLabelFormatter
: Dygraph
.dateAxisFormatter
,
128 xValueFormatter
: Dygraph
.dateString_
,
129 xValueParser
: Dygraph
.dateParser
,
130 xTicker
: Dygraph
.dateTicker
,
137 wilsonInterval
: true, // only relevant if fractions is true
141 connectSeparatedPoints
: false,
144 hideOverlayOnMouseOut
: true,
146 // TODO(danvk): support 'onmouseover' and 'never', and remove synonyms.
147 legend
: 'onmouseover', // the only relevant value at the moment is 'always'.
152 // Sizes of the various chart labels.
160 axisLineColor
: "black",
161 "axisLineWidth": 0.5,
162 "axisLabelColor": "black",
163 "axisLabelFont": "Arial", // TODO(danvk): is this implemented?
164 "axisLabelWidth": 50,
167 "gridLineColor": "rgb(128,128,128)",
169 interactionModel
: null // will be set to Dygraph.defaultInteractionModel.
172 // Various logging levels.
178 // Directions for panning and zooming. Use bit operations when combined
179 // values are possible.
180 Dygraph
.HORIZONTAL
= 1;
181 Dygraph
.VERTICAL
= 2;
183 // Used for initializing annotation CSS rules only once.
184 Dygraph
.addedAnnotationCSS
= false;
188 * Return the 2d context for a dygraph canvas.
190 * This method is only exposed for the sake of replacing the function in
191 * automated tests, e.g.
193 * var oldFunc = Dygraph.getContext();
194 * Dygraph.getContext = function(canvas) {
195 * var realContext = oldFunc(canvas);
196 * return new Proxy(realContext);
199 Dygraph
.getContext
= function(canvas
) {
200 return canvas
.getContext("2d");
203 Dygraph
.prototype.__old_init__
= function(div
, file
, labels
, attrs
) {
204 // Labels is no longer a constructor parameter, since it's typically set
205 // directly from the data source. It also conains a name for the x-axis,
206 // which the previous constructor form did not.
207 if (labels
!= null) {
208 var new_labels
= ["Date"];
209 for (var i
= 0; i
< labels
.length
; i
++) new_labels
.push(labels
[i
]);
210 Dygraph
.update(attrs
, { 'labels': new_labels
});
212 this.__init__(div
, file
, attrs
);
216 * Initializes the Dygraph. This creates a new DIV and constructs the PlotKit
217 * and context <canvas> inside of it. See the constructor for details.
219 * @param {Element} div the Element to render the graph into.
220 * @param {String | Function} file Source data
221 * @param {Object} attrs Miscellaneous other options
224 Dygraph
.prototype.__init__
= function(div
, file
, attrs
) {
225 // Hack for IE: if we're using excanvas and the document hasn't finished
226 // loading yet (and hence may not have initialized whatever it needs to
227 // initialize), then keep calling this routine periodically until it has.
228 if (/MSIE/.test(navigator
.userAgent
) && !window
.opera
&&
229 typeof(G_vmlCanvasManager
) != 'undefined' &&
230 document
.readyState
!= 'complete') {
232 setTimeout(function() { self
.__init__(div
, file
, attrs
) }, 100);
235 // Support two-argument constructor
236 if (attrs
== null) { attrs
= {}; }
238 // Copy the important bits into the object
239 // TODO(danvk): most of these should just stay in the attrs_ dictionary.
242 this.rollPeriod_
= attrs
.rollPeriod
|| Dygraph
.DEFAULT_ROLL_PERIOD
;
243 this.previousVerticalX_
= -1;
244 this.fractions_
= attrs
.fractions
|| false;
245 this.dateWindow_
= attrs
.dateWindow
|| null;
247 this.wilsonInterval_
= attrs
.wilsonInterval
|| true;
248 this.is_initial_draw_
= true;
249 this.annotations_
= [];
251 // Zoomed indicators - These indicate when the graph has been zoomed and on what axis.
252 this.zoomed_x_
= false;
253 this.zoomed_y_
= false;
255 // Clear the div. This ensure that, if multiple dygraphs are passed the same
256 // div, then only one will be drawn.
259 // If the div isn't already sized then inherit from our attrs or
260 // give it a default size.
261 if (div
.style
.width
== '') {
262 div
.style
.width
= (attrs
.width
|| Dygraph
.DEFAULT_WIDTH
) + "px";
264 if (div
.style
.height
== '') {
265 div
.style
.height
= (attrs
.height
|| Dygraph
.DEFAULT_HEIGHT
) + "px";
267 this.width_
= parseInt(div
.style
.width
, 10);
268 this.height_
= parseInt(div
.style
.height
, 10);
269 // The div might have been specified as percent of the current window size,
270 // convert that to an appropriate number of pixels.
271 if (div
.style
.width
.indexOf("%") == div
.style
.width
.length
- 1) {
272 this.width_
= div
.offsetWidth
;
274 if (div
.style
.height
.indexOf("%") == div
.style
.height
.length
- 1) {
275 this.height_
= div
.offsetHeight
;
278 if (this.width_
== 0) {
279 this.error("dygraph has zero width. Please specify a width in pixels.");
281 if (this.height_
== 0) {
282 this.error("dygraph has zero height. Please specify a height in pixels.");
285 // TODO(danvk): set fillGraph to be part of attrs_ here, not user_attrs_.
286 if (attrs
['stackedGraph']) {
287 attrs
['fillGraph'] = true;
288 // TODO(nikhilk): Add any other stackedGraph checks here.
291 // Dygraphs has many options, some of which interact with one another.
292 // To keep track of everything, we maintain two sets of options:
294 // this.user_attrs_ only options explicitly set by the user.
295 // this.attrs_ defaults, options derived from user_attrs_, data.
297 // Options are then accessed this.attr_('attr'), which first looks at
298 // user_attrs_ and then computed attrs_. This way Dygraphs can set intelligent
299 // defaults without overriding behavior that the user specifically asks for.
300 this.user_attrs_
= {};
301 Dygraph
.update(this.user_attrs_
, attrs
);
304 Dygraph
.update(this.attrs_
, Dygraph
.DEFAULT_ATTRS
);
306 this.boundaryIds_
= [];
308 // Make a note of whether labels will be pulled from the CSV file.
309 this.labelsFromCSV_
= (this.attr_("labels") == null);
311 // Create the containing DIV and other interactive elements
312 this.createInterface_();
318 * Returns the zoomed status of the chart for one or both axes.
320 * Axis is an optional parameter. Can be set to 'x' or 'y'.
322 * The zoomed status for an axis is set whenever a user zooms using the mouse
323 * or when the dateWindow or valueRange are updated (unless the isZoomedIgnoreProgrammaticZoom
324 * option is also specified).
326 Dygraph
.prototype.isZoomed
= function(axis
) {
327 if (axis
== null) return this.zoomed_x_
|| this.zoomed_y_
;
328 if (axis
== 'x') return this.zoomed_x_
;
329 if (axis
== 'y') return this.zoomed_y_
;
330 throw "axis parameter to Dygraph.isZoomed must be missing, 'x' or 'y'.";
334 * Returns information about the Dygraph object, including its containing ID.
336 Dygraph
.prototype.toString
= function() {
337 var maindiv
= this.maindiv_
;
338 var id
= (maindiv
&& maindiv
.id
) ? maindiv
.id
: maindiv
339 return "[Dygraph " + id
+ "]";
344 * Returns the value of an option. This may be set by the user (either in the
345 * constructor or by calling updateOptions) or by dygraphs, and may be set to a
347 * @param { String } name The name of the option, e.g. 'rollPeriod'.
348 * @param { String } [seriesName] The name of the series to which the option
349 * will be applied. If no per-series value of this option is available, then
350 * the global value is returned. This is optional.
351 * @return { ... } The value of the option.
353 Dygraph
.prototype.attr_
= function(name
, seriesName
) {
354 // <REMOVE_FOR_COMBINED>
355 if (typeof(Dygraph
.OPTIONS_REFERENCE
) === 'undefined') {
356 this.error('Must include options reference JS for testing');
357 } else if (!Dygraph
.OPTIONS_REFERENCE
.hasOwnProperty(name
)) {
358 this.error('Dygraphs is using property ' + name
+ ', which has no entry ' +
359 'in the Dygraphs.OPTIONS_REFERENCE listing.');
360 // Only log this error once.
361 Dygraph
.OPTIONS_REFERENCE
[name
] = true;
363 // </REMOVE_FOR_COMBINED
>
365 typeof(this.user_attrs_
[seriesName
]) != 'undefined' &&
366 this.user_attrs_
[seriesName
] != null &&
367 typeof(this.user_attrs_
[seriesName
][name
]) != 'undefined') {
368 return this.user_attrs_
[seriesName
][name
];
369 } else if (typeof(this.user_attrs_
[name
]) != 'undefined') {
370 return this.user_attrs_
[name
];
371 } else if (typeof(this.attrs_
[name
]) != 'undefined') {
372 return this.attrs_
[name
];
378 // TODO(danvk): any way I can get the line numbers to be this.warn call?
381 * Log an error on the JS console at the given severity.
382 * @param { Integer } severity One of Dygraph.{DEBUG,INFO,WARNING,ERROR}
383 * @param { String } The message to log.
385 Dygraph
.prototype.log
= function(severity
, message
) {
386 if (typeof(console
) != 'undefined') {
389 console
.debug('dygraphs: ' + message
);
392 console
.info('dygraphs: ' + message
);
394 case Dygraph
.WARNING
:
395 console
.warn('dygraphs: ' + message
);
398 console
.error('dygraphs: ' + message
);
405 Dygraph
.prototype.info
= function(message
) {
406 this.log(Dygraph
.INFO
, message
);
410 Dygraph
.prototype.warn
= function(message
) {
411 this.log(Dygraph
.WARNING
, message
);
415 Dygraph
.prototype.error
= function(message
) {
416 this.log(Dygraph
.ERROR
, message
);
420 * Returns the current rolling period, as set by the user or an option.
421 * @return {Number} The number of points in the rolling window
423 Dygraph
.prototype.rollPeriod
= function() {
424 return this.rollPeriod_
;
428 * Returns the currently-visible x-range. This can be affected by zooming,
429 * panning or a call to updateOptions.
430 * Returns a two-element array: [left, right].
431 * If the Dygraph has dates on the x-axis, these will be millis since epoch.
433 Dygraph
.prototype.xAxisRange
= function() {
434 return this.dateWindow_
? this.dateWindow_
: this.xAxisExtremes();
438 * Returns the lower- and upper-bound x-axis values of the
441 Dygraph
.prototype.xAxisExtremes
= function() {
442 var left
= this.rawData_
[0][0];
443 var right
= this.rawData_
[this.rawData_
.length
- 1][0];
444 return [left
, right
];
448 * Returns the currently-visible y-range for an axis. This can be affected by
449 * zooming, panning or a call to updateOptions. Axis indices are zero-based. If
450 * called with no arguments, returns the range of the first axis.
451 * Returns a two-element array: [bottom, top].
453 Dygraph
.prototype.yAxisRange
= function(idx
) {
454 if (typeof(idx
) == "undefined") idx
= 0;
455 if (idx
< 0 || idx
>= this.axes_
.length
) return null;
456 return [ this.axes_
[idx
].computedValueRange
[0],
457 this.axes_
[idx
].computedValueRange
[1] ];
461 * Returns the currently-visible y-ranges for each axis. This can be affected by
462 * zooming, panning, calls to updateOptions, etc.
463 * Returns an array of [bottom, top] pairs, one for each y-axis.
465 Dygraph
.prototype.yAxisRanges
= function() {
467 for (var i
= 0; i
< this.axes_
.length
; i
++) {
468 ret
.push(this.yAxisRange(i
));
473 // TODO(danvk): use these functions throughout dygraphs.
475 * Convert from data coordinates to canvas/div X/Y coordinates.
476 * If specified, do this conversion for the coordinate system of a particular
477 * axis. Uses the first axis by default.
478 * Returns a two-element array: [X, Y]
480 * Note: use toDomXCoord instead of toDomCoords(x, null) and use toDomYCoord
481 * instead of toDomCoords(null, y, axis).
483 Dygraph
.prototype.toDomCoords
= function(x
, y
, axis
) {
484 return [ this.toDomXCoord(x
), this.toDomYCoord(y
, axis
) ];
488 * Convert from data x coordinates to canvas/div X coordinate.
489 * If specified, do this conversion for the coordinate system of a particular
491 * Returns a single value or null if x is null.
493 Dygraph
.prototype.toDomXCoord
= function(x
) {
498 var area
= this.plotter_
.area
;
499 var xRange
= this.xAxisRange();
500 return area
.x
+ (x
- xRange
[0]) / (xRange
[1] - xRange
[0]) * area
.w
;
504 * Convert from data x coordinates to canvas/div Y coordinate and optional
505 * axis. Uses the first axis by default.
507 * returns a single value or null if y is null.
509 Dygraph
.prototype.toDomYCoord
= function(y
, axis
) {
510 var pct
= this.toPercentYCoord(y
, axis
);
515 var area
= this.plotter_
.area
;
516 return area
.y
+ pct
* area
.h
;
520 * Convert from canvas/div coords to data coordinates.
521 * If specified, do this conversion for the coordinate system of a particular
522 * axis. Uses the first axis by default.
523 * Returns a two-element array: [X, Y].
525 * Note: use toDataXCoord instead of toDataCoords(x, null) and use toDataYCoord
526 * instead of toDataCoords(null, y, axis).
528 Dygraph
.prototype.toDataCoords
= function(x
, y
, axis
) {
529 return [ this.toDataXCoord(x
), this.toDataYCoord(y
, axis
) ];
533 * Convert from canvas/div x coordinate to data coordinate.
535 * If x is null, this returns null.
537 Dygraph
.prototype.toDataXCoord
= function(x
) {
542 var area
= this.plotter_
.area
;
543 var xRange
= this.xAxisRange();
544 return xRange
[0] + (x
- area
.x
) / area
.w
* (xRange
[1] - xRange
[0]);
548 * Convert from canvas/div y coord to value.
550 * If y is null, this returns null.
551 * if axis is null, this uses the first axis.
553 Dygraph
.prototype.toDataYCoord
= function(y
, axis
) {
558 var area
= this.plotter_
.area
;
559 var yRange
= this.yAxisRange(axis
);
561 if (typeof(axis
) == "undefined") axis
= 0;
562 if (!this.axes_
[axis
].logscale
) {
563 return yRange
[0] + (area
.h
- y
) / area
.h
* (yRange
[1] - yRange
[0]);
565 // Computing the inverse of toDomCoord.
566 var pct
= (y
- area
.y
) / area
.h
568 // Computing the inverse of toPercentYCoord. The function was arrived at with
569 // the following steps:
571 // Original calcuation:
572 // pct = (logr1 - Dygraph.log10(y)) / (logr1
- Dygraph
.log10(yRange
[0]));
574 // Move denominator to both sides:
575 // pct * (logr1 - Dygraph.log10(yRange[0])) = logr1 - Dygraph.log10(y);
577 // subtract logr1, and take the negative value.
578 // logr1 - (pct * (logr1 - Dygraph.log10(yRange[0]))) = Dygraph.log10(y);
580 // Swap both sides of the equation, and we can compute the log of the
581 // return value. Which means we just need to use that as the exponent in
583 // Dygraph.log10(y) = logr1 - (pct * (logr1 - Dygraph.log10(yRange[0])));
585 var logr1
= Dygraph
.log10(yRange
[1]);
586 var exponent
= logr1
- (pct
* (logr1
- Dygraph
.log10(yRange
[0])));
587 var value
= Math
.pow(Dygraph
.LOG_SCALE
, exponent
);
593 * Converts a y for an axis to a percentage from the top to the
594 * bottom of the drawing area.
596 * If the coordinate represents a value visible on the canvas, then
597 * the value will be between 0 and 1, where 0 is the top of the canvas.
598 * However, this method will return values outside the range, as
599 * values can fall outside the canvas.
601 * If y is null, this returns null.
602 * if axis is null, this uses the first axis.
604 * @param { Number } y The data y-coordinate.
605 * @param { Number } [axis] The axis number on which the data coordinate lives.
606 * @return { Number } A fraction in [0, 1] where 0 = the top edge.
608 Dygraph
.prototype.toPercentYCoord
= function(y
, axis
) {
612 if (typeof(axis
) == "undefined") axis
= 0;
614 var area
= this.plotter_
.area
;
615 var yRange
= this.yAxisRange(axis
);
618 if (!this.axes_
[axis
].logscale
) {
619 // yRange[1] - y is unit distance from the bottom.
620 // yRange[1] - yRange[0] is the scale of the range.
621 // (yRange[1] - y) / (yRange
[1] - yRange
[0]) is the
% from the bottom
.
622 pct
= (yRange
[1] - y
) / (yRange
[1] - yRange
[0]);
624 var logr1
= Dygraph
.log10(yRange
[1]);
625 pct
= (logr1
- Dygraph
.log10(y
)) / (logr1
- Dygraph
.log10(yRange
[0]));
631 * Converts an x value to a percentage from the left to the right of
634 * If the coordinate represents a value visible on the canvas, then
635 * the value will be between 0 and 1, where 0 is the left of the canvas.
636 * However, this method will return values outside the range, as
637 * values can fall outside the canvas.
639 * If x is null, this returns null.
640 * @param { Number } x The data x-coordinate.
641 * @return { Number } A fraction in [0, 1] where 0 = the left edge.
643 Dygraph
.prototype.toPercentXCoord
= function(x
) {
648 var xRange
= this.xAxisRange();
649 return (x
- xRange
[0]) / (xRange
[1] - xRange
[0]);
653 * Returns the number of columns (including the independent variable).
654 * @return { Integer } The number of columns.
656 Dygraph
.prototype.numColumns
= function() {
657 return this.rawData_
[0].length
;
661 * Returns the number of rows (excluding any header/label row).
662 * @return { Integer } The number of rows, less any header.
664 Dygraph
.prototype.numRows
= function() {
665 return this.rawData_
.length
;
669 * Returns the value in the given row and column. If the row and column exceed
670 * the bounds on the data, returns null. Also returns null if the value is
672 * @param { Number} row The row number of the data (0-based). Row 0 is the
673 * first row of data, not a header row.
674 * @param { Number} col The column number of the data (0-based)
675 * @return { Number } The value in the specified cell or null if the row/col
678 Dygraph
.prototype.getValue
= function(row
, col
) {
679 if (row
< 0 || row
> this.rawData_
.length
) return null;
680 if (col
< 0 || col
> this.rawData_
[row
].length
) return null;
682 return this.rawData_
[row
][col
];
687 * Add an event handler. This smooths a difference between IE and the rest of
689 * @param { DOM element } el The element to add the event to.
690 * @param { String } evt The name of the event, e.g. 'click' or 'mousemove'.
691 * @param { Function } fn The function to call on the event. The function takes
692 * one parameter: the event object.
694 Dygraph
.addEvent
= function(el
, evt
, fn
) {
695 var normed_fn
= function(e
) {
696 if (!e
) var e
= window
.event
;
699 if (window
.addEventListener
) { // Mozilla, Netscape, Firefox
700 el
.addEventListener(evt
, normed_fn
, false);
702 el
.attachEvent('on' + evt
, normed_fn
);
709 * Cancels further processing of an event. This is useful to prevent default
710 * browser actions, e.g. highlighting text on a double-click.
711 * Based on the article at
712 * http://www.switchonthecode.com/tutorials/javascript-tutorial-the-scroll-wheel
713 * @param { Event } e The event whose normal behavior should be canceled.
715 Dygraph
.cancelEvent
= function(e
) {
716 e
= e
? e
: window
.event
;
717 if (e
.stopPropagation
) {
720 if (e
.preventDefault
) {
723 e
.cancelBubble
= true;
725 e
.returnValue
= false;
731 * Generates interface elements for the Dygraph: a containing div, a div to
732 * display the current point, and a textbox to adjust the rolling average
733 * period. Also creates the Renderer/Layout elements.
736 Dygraph
.prototype.createInterface_
= function() {
737 // Create the all-enclosing graph div
738 var enclosing
= this.maindiv_
;
740 this.graphDiv
= document
.createElement("div");
741 this.graphDiv
.style
.width
= this.width_
+ "px";
742 this.graphDiv
.style
.height
= this.height_
+ "px";
743 enclosing
.appendChild(this.graphDiv
);
745 // Create the canvas for interactive parts of the chart.
746 this.canvas_
= Dygraph
.createCanvas();
747 this.canvas_
.style
.position
= "absolute";
748 this.canvas_
.width
= this.width_
;
749 this.canvas_
.height
= this.height_
;
750 this.canvas_
.style
.width
= this.width_
+ "px"; // for IE
751 this.canvas_
.style
.height
= this.height_
+ "px"; // for IE
753 this.canvas_ctx_
= Dygraph
.getContext(this.canvas_
);
755 // ... and for static parts of the chart.
756 this.hidden_
= this.createPlotKitCanvas_(this.canvas_
);
757 this.hidden_ctx_
= Dygraph
.getContext(this.hidden_
);
759 // The interactive parts of the graph are drawn on top of the chart.
760 this.graphDiv
.appendChild(this.hidden_
);
761 this.graphDiv
.appendChild(this.canvas_
);
762 this.mouseEventElement_
= this.canvas_
;
765 Dygraph
.addEvent(this.mouseEventElement_
, 'mousemove', function(e
) {
766 dygraph
.mouseMove_(e
);
768 Dygraph
.addEvent(this.mouseEventElement_
, 'mouseout', function(e
) {
769 dygraph
.mouseOut_(e
);
772 // Create the grapher
773 this.layout_
= new DygraphLayout(this);
775 // TODO(danvk): why does the Renderer need its own set of options?
776 this.renderOptions_
= { colorScheme
: this.colors_
,
778 axisLineWidth
: Dygraph
.AXIS_LINE_WIDTH
};
779 Dygraph
.update(this.renderOptions_
, this.attrs_
);
780 Dygraph
.update(this.renderOptions_
, this.user_attrs_
);
782 this.createStatusMessage_();
783 this.createDragInterface_();
787 * Detach DOM elements in the dygraph and null out all data references.
788 * Calling this when you're done with a dygraph can dramatically reduce memory
789 * usage. See, e.g., the tests/perf.html example.
791 Dygraph
.prototype.destroy
= function() {
792 var removeRecursive
= function(node
) {
793 while (node
.hasChildNodes()) {
794 removeRecursive(node
.firstChild
);
795 node
.removeChild(node
.firstChild
);
798 removeRecursive(this.maindiv_
);
800 var nullOut
= function(obj
) {
802 if (typeof(obj
[n
]) === 'object') {
808 // These may not all be necessary, but it can't hurt...
809 nullOut(this.layout_
);
810 nullOut(this.plotter_
);
815 * Creates the canvas on which the chart will be drawn. Only the Renderer ever
816 * draws on this particular canvas. All Dygraph work (i.e. drawing hover dots
817 * or the zoom rectangles) is done on this.canvas_.
818 * @param {Object} canvas The Dygraph canvas over which to overlay the plot
819 * @return {Object} The newly-created canvas
822 Dygraph
.prototype.createPlotKitCanvas_
= function(canvas
) {
823 var h
= Dygraph
.createCanvas();
824 h
.style
.position
= "absolute";
825 // TODO(danvk): h should be offset from canvas. canvas needs to include
826 // some extra area to make it easier to zoom in on the far left and far
827 // right. h needs to be precisely the plot area, so that clipping occurs.
828 h
.style
.top
= canvas
.style
.top
;
829 h
.style
.left
= canvas
.style
.left
;
830 h
.width
= this.width_
;
831 h
.height
= this.height_
;
832 h
.style
.width
= this.width_
+ "px"; // for IE
833 h
.style
.height
= this.height_
+ "px"; // for IE
838 * Convert hsv values to an rgb(r,g,b) string. Taken from MochiKit.Color. This
839 * is used to generate default series colors which are evenly spaced on the
841 * @param { Number } hue Range is 0.0-1.0.
842 * @param { Number } saturation Range is 0.0-1.0.
843 * @param { Number } value Range is 0.0-1.0.
844 * @return { String } "rgb(r,g,b)" where r, g and b range from 0-255.
847 Dygraph
.hsvToRGB
= function (hue
, saturation
, value
) {
851 if (saturation
=== 0) {
856 var i
= Math
.floor(hue
* 6);
857 var f
= (hue
* 6) - i
;
858 var p
= value
* (1 - saturation
);
859 var q
= value
* (1 - (saturation
* f
));
860 var t
= value
* (1 - (saturation
* (1 - f
)));
862 case 1: red
= q
; green
= value
; blue
= p
; break;
863 case 2: red
= p
; green
= value
; blue
= t
; break;
864 case 3: red
= p
; green
= q
; blue
= value
; break;
865 case 4: red
= t
; green
= p
; blue
= value
; break;
866 case 5: red
= value
; green
= p
; blue
= q
; break;
867 case 6: // fall through
868 case 0: red
= value
; green
= t
; blue
= p
; break;
871 red
= Math
.floor(255 * red
+ 0.5);
872 green
= Math
.floor(255 * green
+ 0.5);
873 blue
= Math
.floor(255 * blue
+ 0.5);
874 return 'rgb(' + red
+ ',' + green
+ ',' + blue
+ ')';
879 * Generate a set of distinct colors for the data series. This is done with a
880 * color wheel. Saturation/Value are customizable, and the hue is
881 * equally-spaced around the color wheel. If a custom set of colors is
882 * specified, that is used instead.
885 Dygraph
.prototype.setColors_
= function() {
886 // TODO(danvk): compute this directly into this.attrs_['colorScheme'] and do
887 // away with this.renderOptions_.
888 var num
= this.attr_("labels").length
- 1;
890 var colors
= this.attr_('colors');
892 var sat
= this.attr_('colorSaturation') || 1.0;
893 var val
= this.attr_('colorValue') || 0.5;
894 var half
= Math
.ceil(num
/ 2);
895 for (var i
= 1; i
<= num
; i
++) {
896 if (!this.visibility()[i
-1]) continue;
897 // alternate colors for high contrast.
898 var idx
= i
% 2 ? Math
.ceil(i
/ 2) : (half + i / 2);
899 var hue
= (1.0 * idx
/ (1 + num
));
900 this.colors_
.push(Dygraph
.hsvToRGB(hue
, sat
, val
));
903 for (var i
= 0; i
< num
; i
++) {
904 if (!this.visibility()[i
]) continue;
905 var colorStr
= colors
[i
% colors
.length
];
906 this.colors_
.push(colorStr
);
910 // TODO(danvk): update this w/r
/t/ the
new options system
.
911 this.renderOptions_
.colorScheme
= this.colors_
;
912 Dygraph
.update(this.plotter_
.options
, this.renderOptions_
);
916 * Return the list of colors. This is either the list of colors passed in the
917 * attributes or the autogenerated list of rgb(r,g,b) strings.
918 * @return {Array<string>} The list of colors.
920 Dygraph
.prototype.getColors
= function() {
924 // The following functions are from quirksmode.org with a modification for Safari from
925 // http://blog.firetree.net/2005/07/04/javascript-find-position/
926 // http://www.quirksmode.org/js
/findpos
.html
929 Dygraph
.findPosX
= function(obj
) {
934 curleft
+= obj
.offsetLeft
;
935 if(!obj
.offsetParent
)
937 obj
= obj
.offsetParent
;
946 Dygraph
.findPosY
= function(obj
) {
951 curtop
+= obj
.offsetTop
;
952 if(!obj
.offsetParent
)
954 obj
= obj
.offsetParent
;
963 * Create the div that contains information on the selected point(s)
964 * This goes in the top right of the canvas, unless an external div has already
968 Dygraph
.prototype.createStatusMessage_
= function() {
969 var userLabelsDiv
= this.user_attrs_
["labelsDiv"];
970 if (userLabelsDiv
&& null != userLabelsDiv
971 && (typeof(userLabelsDiv
) == "string" || userLabelsDiv
instanceof String
)) {
972 this.user_attrs_
["labelsDiv"] = document
.getElementById(userLabelsDiv
);
974 if (!this.attr_("labelsDiv")) {
975 var divWidth
= this.attr_('labelsDivWidth');
977 "position": "absolute",
980 "width": divWidth
+ "px",
982 "left": (this.width_
- divWidth
- 2) + "px",
983 "background": "white",
985 "overflow": "hidden"};
986 Dygraph
.update(messagestyle
, this.attr_('labelsDivStyles'));
987 var div
= document
.createElement("div");
988 for (var name
in messagestyle
) {
989 if (messagestyle
.hasOwnProperty(name
)) {
990 div
.style
[name
] = messagestyle
[name
];
993 this.graphDiv
.appendChild(div
);
994 this.attrs_
.labelsDiv
= div
;
999 * Position the labels div so that:
1000 * - its right edge is flush with the right edge of the charting area
1001 * - its top edge is flush with the top edge of the charting area
1004 Dygraph
.prototype.positionLabelsDiv_
= function() {
1005 // Don't touch a user-specified labelsDiv.
1006 if (this.user_attrs_
.hasOwnProperty("labelsDiv")) return;
1008 var area
= this.plotter_
.area
;
1009 var div
= this.attr_("labelsDiv");
1010 div
.style
.left
= area
.x
+ area
.w
- this.attr_("labelsDivWidth") - 1 + "px";
1011 div
.style
.top
= area
.y
+ "px";
1015 * Create the text box to adjust the averaging period
1018 Dygraph
.prototype.createRollInterface_
= function() {
1019 // Create a roller if one doesn't exist already.
1020 if (!this.roller_
) {
1021 this.roller_
= document
.createElement("input");
1022 this.roller_
.type
= "text";
1023 this.roller_
.style
.display
= "none";
1024 this.graphDiv
.appendChild(this.roller_
);
1027 var display
= this.attr_('showRoller') ? 'block' : 'none';
1029 var area
= this.plotter_
.area
;
1030 var textAttr
= { "position": "absolute",
1032 "top": (area
.y
+ area
.h
- 25) + "px",
1033 "left": (area
.x
+ 1) + "px",
1036 this.roller_
.size
= "2";
1037 this.roller_
.value
= this.rollPeriod_
;
1038 for (var name
in textAttr
) {
1039 if (textAttr
.hasOwnProperty(name
)) {
1040 this.roller_
.style
[name
] = textAttr
[name
];
1045 this.roller_
.onchange
= function() { dygraph
.adjustRoll(dygraph
.roller_
.value
); };
1050 * Returns the x-coordinate of the event in a coordinate system where the
1051 * top-left corner of the page (not the window) is (0,0).
1052 * Taken from MochiKit.Signal
1054 Dygraph
.pageX
= function(e
) {
1056 return (!e
.pageX
|| e
.pageX
< 0) ? 0 : e
.pageX
;
1059 var b
= document
.body
;
1061 (de
.scrollLeft
|| b
.scrollLeft
) -
1062 (de
.clientLeft
|| 0);
1068 * Returns the y-coordinate of the event in a coordinate system where the
1069 * top-left corner of the page (not the window) is (0,0).
1070 * Taken from MochiKit.Signal
1072 Dygraph
.pageY
= function(e
) {
1074 return (!e
.pageY
|| e
.pageY
< 0) ? 0 : e
.pageY
;
1077 var b
= document
.body
;
1079 (de
.scrollTop
|| b
.scrollTop
) -
1080 (de
.clientTop
|| 0);
1086 * Converts page the x-coordinate of the event to pixel x-coordinates on the
1087 * canvas (i.e. DOM Coords).
1089 Dygraph
.prototype.dragGetX_
= function(e
, context
) {
1090 return Dygraph
.pageX(e
) - context
.px
1095 * Converts page the y-coordinate of the event to pixel y-coordinates on the
1096 * canvas (i.e. DOM Coords).
1098 Dygraph
.prototype.dragGetY_
= function(e
, context
) {
1099 return Dygraph
.pageY(e
) - context
.py
1103 * A collection of functions to facilitate build custom interaction models.
1106 Dygraph
.Interaction
= {};
1109 * Called in response to an interaction model operation that
1110 * should start the default panning behavior.
1112 * It's used in the default callback for "mousedown" operations.
1113 * Custom interaction model builders can use it to provide the default
1116 * @param { Event } event the event object which led to the startPan call.
1117 * @param { Dygraph} g The dygraph on which to act.
1118 * @param { Object} context The dragging context object (with
1119 * dragStartX/dragStartY/etc. properties). This function modifies the context.
1121 Dygraph
.Interaction
.startPan
= function(event
, g
, context
) {
1122 context
.isPanning
= true;
1123 var xRange
= g
.xAxisRange();
1124 context
.dateRange
= xRange
[1] - xRange
[0];
1125 context
.initialLeftmostDate
= xRange
[0];
1126 context
.xUnitsPerPixel
= context
.dateRange
/ (g
.plotter_
.area
.w
- 1);
1128 if (g
.attr_("panEdgeFraction")) {
1129 var maxXPixelsToDraw
= g
.width_
* g
.attr_("panEdgeFraction");
1130 var xExtremes
= g
.xAxisExtremes(); // I REALLY WANT TO CALL THIS xTremes!
1132 var boundedLeftX
= g
.toDomXCoord(xExtremes
[0]) - maxXPixelsToDraw
;
1133 var boundedRightX
= g
.toDomXCoord(xExtremes
[1]) + maxXPixelsToDraw
;
1135 var boundedLeftDate
= g
.toDataXCoord(boundedLeftX
);
1136 var boundedRightDate
= g
.toDataXCoord(boundedRightX
);
1137 context
.boundedDates
= [boundedLeftDate
, boundedRightDate
];
1139 var boundedValues
= [];
1140 var maxYPixelsToDraw
= g
.height_
* g
.attr_("panEdgeFraction");
1142 for (var i
= 0; i
< g
.axes_
.length
; i
++) {
1143 var axis
= g
.axes_
[i
];
1144 var yExtremes
= axis
.extremeRange
;
1146 var boundedTopY
= g
.toDomYCoord(yExtremes
[0], i
) + maxYPixelsToDraw
;
1147 var boundedBottomY
= g
.toDomYCoord(yExtremes
[1], i
) - maxYPixelsToDraw
;
1149 var boundedTopValue
= g
.toDataYCoord(boundedTopY
);
1150 var boundedBottomValue
= g
.toDataYCoord(boundedBottomY
);
1152 boundedValues
[i
] = [boundedTopValue
, boundedBottomValue
];
1154 context
.boundedValues
= boundedValues
;
1157 // Record the range of each y-axis at the start of the drag.
1158 // If any axis has a valueRange or valueWindow, then we want a 2D pan.
1159 context
.is2DPan
= false;
1160 for (var i
= 0; i
< g
.axes_
.length
; i
++) {
1161 var axis
= g
.axes_
[i
];
1162 var yRange
= g
.yAxisRange(i
);
1163 // TODO(konigsberg): These values should be in |context|.
1164 // In log scale, initialTopValue, dragValueRange and unitsPerPixel are log scale.
1165 if (axis
.logscale
) {
1166 axis
.initialTopValue
= Dygraph
.log10(yRange
[1]);
1167 axis
.dragValueRange
= Dygraph
.log10(yRange
[1]) - Dygraph
.log10(yRange
[0]);
1169 axis
.initialTopValue
= yRange
[1];
1170 axis
.dragValueRange
= yRange
[1] - yRange
[0];
1172 axis
.unitsPerPixel
= axis
.dragValueRange
/ (g
.plotter_
.area
.h
- 1);
1174 // While calculating axes, set 2dpan.
1175 if (axis
.valueWindow
|| axis
.valueRange
) context
.is2DPan
= true;
1180 * Called in response to an interaction model operation that
1181 * responds to an event that pans the view.
1183 * It's used in the default callback for "mousemove" operations.
1184 * Custom interaction model builders can use it to provide the default
1187 * @param { Event } event the event object which led to the movePan call.
1188 * @param { Dygraph} g The dygraph on which to act.
1189 * @param { Object} context The dragging context object (with
1190 * dragStartX/dragStartY/etc. properties). This function modifies the context.
1192 Dygraph
.Interaction
.movePan
= function(event
, g
, context
) {
1193 context
.dragEndX
= g
.dragGetX_(event
, context
);
1194 context
.dragEndY
= g
.dragGetY_(event
, context
);
1196 var minDate
= context
.initialLeftmostDate
-
1197 (context
.dragEndX
- context
.dragStartX
) * context
.xUnitsPerPixel
;
1198 if (context
.boundedDates
) {
1199 minDate
= Math
.max(minDate
, context
.boundedDates
[0]);
1201 var maxDate
= minDate
+ context
.dateRange
;
1202 if (context
.boundedDates
) {
1203 if (maxDate
> context
.boundedDates
[1]) {
1204 // Adjust minDate, and recompute maxDate.
1205 minDate
= minDate
- (maxDate
- context
.boundedDates
[1]);
1206 maxDate
= minDate
+ context
.dateRange
;
1210 g
.dateWindow_
= [minDate
, maxDate
];
1212 // y-axis scaling is automatic unless this is a full 2D pan.
1213 if (context
.is2DPan
) {
1214 // Adjust each axis appropriately.
1215 for (var i
= 0; i
< g
.axes_
.length
; i
++) {
1216 var axis
= g
.axes_
[i
];
1218 var pixelsDragged
= context
.dragEndY
- context
.dragStartY
;
1219 var unitsDragged
= pixelsDragged
* axis
.unitsPerPixel
;
1221 var boundedValue
= context
.boundedValues
? context
.boundedValues
[i
] : null;
1223 // In log scale, maxValue and minValue are the logs of those values.
1224 var maxValue
= axis
.initialTopValue
+ unitsDragged
;
1226 maxValue
= Math
.min(maxValue
, boundedValue
[1]);
1228 var minValue
= maxValue
- axis
.dragValueRange
;
1230 if (minValue
< boundedValue
[0]) {
1231 // Adjust maxValue, and recompute minValue.
1232 maxValue
= maxValue
- (minValue
- boundedValue
[0]);
1233 minValue
= maxValue
- axis
.dragValueRange
;
1236 if (axis
.logscale
) {
1237 axis
.valueWindow
= [ Math
.pow(Dygraph
.LOG_SCALE
, minValue
),
1238 Math
.pow(Dygraph
.LOG_SCALE
, maxValue
) ];
1240 axis
.valueWindow
= [ minValue
, maxValue
];
1249 * Called in response to an interaction model operation that
1250 * responds to an event that ends panning.
1252 * It's used in the default callback for "mouseup" operations.
1253 * Custom interaction model builders can use it to provide the default
1256 * @param { Event } event the event object which led to the startZoom call.
1257 * @param { Dygraph} g The dygraph on which to act.
1258 * @param { Object} context The dragging context object (with
1259 * dragStartX/dragStartY/etc. properties). This function modifies the context.
1261 Dygraph
.Interaction
.endPan
= function(event
, g
, context
) {
1262 // TODO(konigsberg): Clear the context data from the axis.
1263 // TODO(konigsberg): mouseup should just delete the
1264 // context object, and mousedown should create a new one.
1265 context
.isPanning
= false;
1266 context
.is2DPan
= false;
1267 context
.initialLeftmostDate
= null;
1268 context
.dateRange
= null;
1269 context
.valueRange
= null;
1270 context
.boundedDates
= null;
1271 context
.boundedValues
= null;
1275 * Called in response to an interaction model operation that
1276 * responds to an event that starts zooming.
1278 * It's used in the default callback for "mousedown" operations.
1279 * Custom interaction model builders can use it to provide the default
1282 * @param { Event } event the event object which led to the startZoom call.
1283 * @param { Dygraph} g The dygraph on which to act.
1284 * @param { Object} context The dragging context object (with
1285 * dragStartX/dragStartY/etc. properties). This function modifies the context.
1287 Dygraph
.Interaction
.startZoom
= function(event
, g
, context
) {
1288 context
.isZooming
= true;
1292 * Called in response to an interaction model operation that
1293 * responds to an event that defines zoom boundaries.
1295 * It's used in the default callback for "mousemove" operations.
1296 * Custom interaction model builders can use it to provide the default
1299 * @param { Event } event the event object which led to the moveZoom call.
1300 * @param { Dygraph} g The dygraph on which to act.
1301 * @param { Object} context The dragging context object (with
1302 * dragStartX/dragStartY/etc. properties). This function modifies the context.
1304 Dygraph
.Interaction
.moveZoom
= function(event
, g
, context
) {
1305 context
.dragEndX
= g
.dragGetX_(event
, context
);
1306 context
.dragEndY
= g
.dragGetY_(event
, context
);
1308 var xDelta
= Math
.abs(context
.dragStartX
- context
.dragEndX
);
1309 var yDelta
= Math
.abs(context
.dragStartY
- context
.dragEndY
);
1311 // drag direction threshold for y axis is twice as large as x axis
1312 context
.dragDirection
= (xDelta
< yDelta
/ 2) ? Dygraph
.VERTICAL
: Dygraph
.HORIZONTAL
;
1315 context
.dragDirection
,
1320 context
.prevDragDirection
,
1324 context
.prevEndX
= context
.dragEndX
;
1325 context
.prevEndY
= context
.dragEndY
;
1326 context
.prevDragDirection
= context
.dragDirection
;
1330 * Called in response to an interaction model operation that
1331 * responds to an event that performs a zoom based on previously defined
1334 * It's used in the default callback for "mouseup" operations.
1335 * Custom interaction model builders can use it to provide the default
1338 * @param { Event } event the event object which led to the endZoom call.
1339 * @param { Dygraph} g The dygraph on which to end the zoom.
1340 * @param { Object} context The dragging context object (with
1341 * dragStartX/dragStartY/etc. properties). This function modifies the context.
1343 Dygraph
.Interaction
.endZoom
= function(event
, g
, context
) {
1344 // TODO(konigsberg): Refactor or rename this fn -- it deals with clicks, too.
1345 context
.isZooming
= false;
1346 context
.dragEndX
= g
.dragGetX_(event
, context
);
1347 context
.dragEndY
= g
.dragGetY_(event
, context
);
1348 var regionWidth
= Math
.abs(context
.dragEndX
- context
.dragStartX
);
1349 var regionHeight
= Math
.abs(context
.dragEndY
- context
.dragStartY
);
1351 if (regionWidth
< 2 && regionHeight
< 2 &&
1352 g
.lastx_
!= undefined
&& g
.lastx_
!= -1) {
1353 // TODO(danvk): pass along more info about the points, e.g. 'x'
1354 if (g
.attr_('clickCallback') != null) {
1355 g
.attr_('clickCallback')(event
, g
.lastx_
, g
.selPoints_
);
1357 if (g
.attr_('pointClickCallback')) {
1358 // check if the click was on a particular point.
1359 var closestIdx
= -1;
1360 var closestDistance
= 0;
1361 for (var i
= 0; i
< g
.selPoints_
.length
; i
++) {
1362 var p
= g
.selPoints_
[i
];
1363 var distance
= Math
.pow(p
.canvasx
- context
.dragEndX
, 2) +
1364 Math
.pow(p
.canvasy
- context
.dragEndY
, 2);
1365 if (closestIdx
== -1 || distance
< closestDistance
) {
1366 closestDistance
= distance
;
1371 // Allow any click within two pixels of the dot.
1372 var radius
= g
.attr_('highlightCircleSize') + 2;
1373 if (closestDistance
<= 5 * 5) {
1374 g
.attr_('pointClickCallback')(event
, g
.selPoints_
[closestIdx
]);
1379 if (regionWidth
>= 10 && context
.dragDirection
== Dygraph
.HORIZONTAL
) {
1380 g
.doZoomX_(Math
.min(context
.dragStartX
, context
.dragEndX
),
1381 Math
.max(context
.dragStartX
, context
.dragEndX
));
1382 } else if (regionHeight
>= 10 && context
.dragDirection
== Dygraph
.VERTICAL
) {
1383 g
.doZoomY_(Math
.min(context
.dragStartY
, context
.dragEndY
),
1384 Math
.max(context
.dragStartY
, context
.dragEndY
));
1386 g
.canvas_ctx_
.clearRect(0, 0, g
.canvas_
.width
, g
.canvas_
.height
);
1388 context
.dragStartX
= null;
1389 context
.dragStartY
= null;
1393 * Default interation model for dygraphs. You can refer to specific elements of
1394 * this when constructing your own interaction model, e.g.:
1395 * g.updateOptions( {
1396 * interactionModel: {
1397 * mousedown: Dygraph.defaultInteractionModel.mousedown
1401 Dygraph
.Interaction
.defaultModel
= {
1402 // Track the beginning of drag events
1403 mousedown
: function(event
, g
, context
) {
1404 context
.initializeMouseDown(event
, g
, context
);
1406 if (event
.altKey
|| event
.shiftKey
) {
1407 Dygraph
.startPan(event
, g
, context
);
1409 Dygraph
.startZoom(event
, g
, context
);
1413 // Draw zoom rectangles when the mouse is down and the user moves around
1414 mousemove
: function(event
, g
, context
) {
1415 if (context
.isZooming
) {
1416 Dygraph
.moveZoom(event
, g
, context
);
1417 } else if (context
.isPanning
) {
1418 Dygraph
.movePan(event
, g
, context
);
1422 mouseup
: function(event
, g
, context
) {
1423 if (context
.isZooming
) {
1424 Dygraph
.endZoom(event
, g
, context
);
1425 } else if (context
.isPanning
) {
1426 Dygraph
.endPan(event
, g
, context
);
1430 // Temporarily cancel the dragging event when the mouse leaves the graph
1431 mouseout
: function(event
, g
, context
) {
1432 if (context
.isZooming
) {
1433 context
.dragEndX
= null;
1434 context
.dragEndY
= null;
1438 // Disable zooming out if panning.
1439 dblclick
: function(event
, g
, context
) {
1440 if (event
.altKey
|| event
.shiftKey
) {
1443 // TODO(konigsberg): replace g.doUnzoom()_ with something that is
1444 // friendlier to public use.
1449 Dygraph
.DEFAULT_ATTRS
.interactionModel
= Dygraph
.Interaction
.defaultModel
;
1451 // old ways of accessing these methods/properties
1452 Dygraph
.defaultInteractionModel
= Dygraph
.Interaction
.defaultModel
;
1453 Dygraph
.endZoom
= Dygraph
.Interaction
.endZoom
;
1454 Dygraph
.moveZoom
= Dygraph
.Interaction
.moveZoom
;
1455 Dygraph
.startZoom
= Dygraph
.Interaction
.startZoom
;
1456 Dygraph
.endPan
= Dygraph
.Interaction
.endPan
;
1457 Dygraph
.movePan
= Dygraph
.Interaction
.movePan
;
1458 Dygraph
.startPan
= Dygraph
.Interaction
.startPan
;
1461 * Set up all the mouse handlers needed to capture dragging behavior for zoom
1465 Dygraph
.prototype.createDragInterface_
= function() {
1467 // Tracks whether the mouse is down right now
1469 isPanning
: false, // is this drag part of a pan?
1470 is2DPan
: false, // if so, is that pan 1- or 2-dimensional?
1475 dragDirection
: null,
1478 prevDragDirection
: null,
1480 // The value on the left side of the graph when a pan operation starts.
1481 initialLeftmostDate
: null,
1483 // The number of units each pixel spans. (This won't be valid for log
1485 xUnitsPerPixel
: null,
1487 // TODO(danvk): update this comment
1488 // The range in second/value units that the viewport encompasses during a
1489 // panning operation.
1492 // Utility function to convert page-wide coordinates to canvas coords
1496 // Values for use with panEdgeFraction, which limit how far outside the
1497 // graph's data boundaries it can be panned.
1498 boundedDates
: null, // [minDate, maxDate]
1499 boundedValues
: null, // [[minValue, maxValue] ...]
1501 initializeMouseDown
: function(event
, g
, context
) {
1502 // prevents mouse drags from selecting page text.
1503 if (event
.preventDefault
) {
1504 event
.preventDefault(); // Firefox, Chrome, etc.
1506 event
.returnValue
= false; // IE
1507 event
.cancelBubble
= true;
1510 context
.px
= Dygraph
.findPosX(g
.canvas_
);
1511 context
.py
= Dygraph
.findPosY(g
.canvas_
);
1512 context
.dragStartX
= g
.dragGetX_(event
, context
);
1513 context
.dragStartY
= g
.dragGetY_(event
, context
);
1517 var interactionModel
= this.attr_("interactionModel");
1519 // Self is the graph.
1522 // Function that binds the graph and context to the handler.
1523 var bindHandler
= function(handler
) {
1524 return function(event
) {
1525 handler(event
, self
, context
);
1529 for (var eventName
in interactionModel
) {
1530 if (!interactionModel
.hasOwnProperty(eventName
)) continue;
1531 Dygraph
.addEvent(this.mouseEventElement_
, eventName
,
1532 bindHandler(interactionModel
[eventName
]));
1535 // If the user releases the mouse button during a drag, but not over the
1536 // canvas, then it doesn't count as a zooming action.
1537 Dygraph
.addEvent(document
, 'mouseup', function(event
) {
1538 if (context
.isZooming
|| context
.isPanning
) {
1539 context
.isZooming
= false;
1540 context
.dragStartX
= null;
1541 context
.dragStartY
= null;
1544 if (context
.isPanning
) {
1545 context
.isPanning
= false;
1546 context
.draggingDate
= null;
1547 context
.dateRange
= null;
1548 for (var i
= 0; i
< self
.axes_
.length
; i
++) {
1549 delete self
.axes_
[i
].draggingValue
;
1550 delete self
.axes_
[i
].dragValueRange
;
1558 * Draw a gray zoom rectangle over the desired area of the canvas. Also clears
1559 * up any previous zoom rectangles that were drawn. This could be optimized to
1560 * avoid extra redrawing, but it's tricky to avoid interactions with the status
1563 * @param {Number} direction the direction of the zoom rectangle. Acceptable
1564 * values are Dygraph.HORIZONTAL and Dygraph.VERTICAL.
1565 * @param {Number} startX The X position where the drag started, in canvas
1567 * @param {Number} endX The current X position of the drag, in canvas coords.
1568 * @param {Number} startY The Y position where the drag started, in canvas
1570 * @param {Number} endY The current Y position of the drag, in canvas coords.
1571 * @param {Number} prevDirection the value of direction on the previous call to
1572 * this function. Used to avoid excess redrawing
1573 * @param {Number} prevEndX The value of endX on the previous call to this
1574 * function. Used to avoid excess redrawing
1575 * @param {Number} prevEndY The value of endY on the previous call to this
1576 * function. Used to avoid excess redrawing
1579 Dygraph
.prototype.drawZoomRect_
= function(direction
, startX
, endX
, startY
,
1580 endY
, prevDirection
, prevEndX
,
1582 var ctx
= this.canvas_ctx_
;
1584 // Clean up from the previous rect if necessary
1585 if (prevDirection
== Dygraph
.HORIZONTAL
) {
1586 ctx
.clearRect(Math
.min(startX
, prevEndX
), 0,
1587 Math
.abs(startX
- prevEndX
), this.height_
);
1588 } else if (prevDirection
== Dygraph
.VERTICAL
){
1589 ctx
.clearRect(0, Math
.min(startY
, prevEndY
),
1590 this.width_
, Math
.abs(startY
- prevEndY
));
1593 // Draw a light-grey rectangle to show the new viewing area
1594 if (direction
== Dygraph
.HORIZONTAL
) {
1595 if (endX
&& startX
) {
1596 ctx
.fillStyle
= "rgba(128,128,128,0.33)";
1597 ctx
.fillRect(Math
.min(startX
, endX
), 0,
1598 Math
.abs(endX
- startX
), this.height_
);
1601 if (direction
== Dygraph
.VERTICAL
) {
1602 if (endY
&& startY
) {
1603 ctx
.fillStyle
= "rgba(128,128,128,0.33)";
1604 ctx
.fillRect(0, Math
.min(startY
, endY
),
1605 this.width_
, Math
.abs(endY
- startY
));
1611 * Zoom to something containing [lowX, highX]. These are pixel coordinates in
1612 * the canvas. The exact zoom window may be slightly larger if there are no data
1613 * points near lowX or highX. Don't confuse this function with doZoomXDates,
1614 * which accepts dates that match the raw data. This function redraws the graph.
1616 * @param {Number} lowX The leftmost pixel value that should be visible.
1617 * @param {Number} highX The rightmost pixel value that should be visible.
1620 Dygraph
.prototype.doZoomX_
= function(lowX
, highX
) {
1621 // Find the earliest and latest dates contained in this canvasx range.
1622 // Convert the call to date ranges of the raw data.
1623 var minDate
= this.toDataXCoord(lowX
);
1624 var maxDate
= this.toDataXCoord(highX
);
1625 this.doZoomXDates_(minDate
, maxDate
);
1629 * Zoom to something containing [minDate, maxDate] values. Don't confuse this
1630 * method with doZoomX which accepts pixel coordinates. This function redraws
1633 * @param {Number} minDate The minimum date that should be visible.
1634 * @param {Number} maxDate The maximum date that should be visible.
1637 Dygraph
.prototype.doZoomXDates_
= function(minDate
, maxDate
) {
1638 this.dateWindow_
= [minDate
, maxDate
];
1639 this.zoomed_x_
= true;
1641 if (this.attr_("zoomCallback")) {
1642 this.attr_("zoomCallback")(minDate
, maxDate
, this.yAxisRanges());
1647 * Zoom to something containing [lowY, highY]. These are pixel coordinates in
1648 * the canvas. This function redraws the graph.
1650 * @param {Number} lowY The topmost pixel value that should be visible.
1651 * @param {Number} highY The lowest pixel value that should be visible.
1654 Dygraph
.prototype.doZoomY_
= function(lowY
, highY
) {
1655 // Find the highest and lowest values in pixel range for each axis.
1656 // Note that lowY (in pixels) corresponds to the max Value (in data coords).
1657 // This is because pixels increase as you go down on the screen, whereas data
1658 // coordinates increase as you go up the screen.
1659 var valueRanges
= [];
1660 for (var i
= 0; i
< this.axes_
.length
; i
++) {
1661 var hi
= this.toDataYCoord(lowY
, i
);
1662 var low
= this.toDataYCoord(highY
, i
);
1663 this.axes_
[i
].valueWindow
= [low
, hi
];
1664 valueRanges
.push([low
, hi
]);
1667 this.zoomed_y_
= true;
1669 if (this.attr_("zoomCallback")) {
1670 var xRange
= this.xAxisRange();
1671 var yRange
= this.yAxisRange();
1672 this.attr_("zoomCallback")(xRange
[0], xRange
[1], this.yAxisRanges());
1677 * Reset the zoom to the original view coordinates. This is the same as
1678 * double-clicking on the graph.
1682 Dygraph
.prototype.doUnzoom_
= function() {
1684 if (this.dateWindow_
!= null) {
1686 this.dateWindow_
= null;
1689 for (var i
= 0; i
< this.axes_
.length
; i
++) {
1690 if (this.axes_
[i
].valueWindow
!= null) {
1692 delete this.axes_
[i
].valueWindow
;
1697 // Putting the drawing operation before the callback because it resets
1699 this.zoomed_x_
= false;
1700 this.zoomed_y_
= false;
1702 if (this.attr_("zoomCallback")) {
1703 var minDate
= this.rawData_
[0][0];
1704 var maxDate
= this.rawData_
[this.rawData_
.length
- 1][0];
1705 this.attr_("zoomCallback")(minDate
, maxDate
, this.yAxisRanges());
1711 * When the mouse moves in the canvas, display information about a nearby data
1712 * point and draw dots over those points in the data series. This function
1713 * takes care of cleanup of previously-drawn dots.
1714 * @param {Object} event The mousemove event from the browser.
1717 Dygraph
.prototype.mouseMove_
= function(event
) {
1718 // This prevents JS errors when mousing over the canvas before data loads.
1719 var points
= this.layout_
.points
;
1720 if (points
=== undefined
) return;
1722 var canvasx
= Dygraph
.pageX(event
) - Dygraph
.findPosX(this.mouseEventElement_
);
1727 // Loop through all the points and find the date nearest to our current
1729 var minDist
= 1e+100;
1731 for (var i
= 0; i
< points
.length
; i
++) {
1732 var point
= points
[i
];
1733 if (point
== null) continue;
1734 var dist
= Math
.abs(point
.canvasx
- canvasx
);
1735 if (dist
> minDist
) continue;
1739 if (idx
>= 0) lastx
= points
[idx
].xval
;
1741 // Extract the points we've selected
1742 this.selPoints_
= [];
1743 var l
= points
.length
;
1744 if (!this.attr_("stackedGraph")) {
1745 for (var i
= 0; i
< l
; i
++) {
1746 if (points
[i
].xval
== lastx
) {
1747 this.selPoints_
.push(points
[i
]);
1751 // Need to 'unstack' points starting from the bottom
1752 var cumulative_sum
= 0;
1753 for (var i
= l
- 1; i
>= 0; i
--) {
1754 if (points
[i
].xval
== lastx
) {
1755 var p
= {}; // Clone the point since we modify it
1756 for (var k
in points
[i
]) {
1757 p
[k
] = points
[i
][k
];
1759 p
.yval
-= cumulative_sum
;
1760 cumulative_sum
+= p
.yval
;
1761 this.selPoints_
.push(p
);
1764 this.selPoints_
.reverse();
1767 if (this.attr_("highlightCallback")) {
1768 var px
= this.lastx_
;
1769 if (px
!== null && lastx
!= px
) {
1770 // only fire if the selected point has changed.
1771 this.attr_("highlightCallback")(event
, lastx
, this.selPoints_
, this.idxToRow_(idx
));
1775 // Save last x position for callbacks.
1776 this.lastx_
= lastx
;
1778 this.updateSelection_();
1782 * Transforms layout_.points index into data row number.
1783 * @param int layout_.points index
1784 * @return int row number, or -1 if none could be found.
1787 Dygraph
.prototype.idxToRow_
= function(idx
) {
1788 if (idx
< 0) return -1;
1790 for (var i
in this.layout_
.datasets
) {
1791 if (idx
< this.layout_
.datasets
[i
].length
) {
1792 return this.boundaryIds_
[0][0]+idx
;
1794 idx
-= this.layout_
.datasets
[i
].length
;
1801 * @param { Number } x The number to consider.
1802 * @return { Boolean } Whether the number is zero or NaN.
1804 // TODO(danvk): rename this function to something like 'isNonZeroNan'.
1805 Dygraph
.isOK
= function(x
) {
1806 return x
&& !isNaN(x
);
1811 * Generates HTML for the legend which is displayed when hovering over the
1812 * chart. If no selected points are specified, a default legend is returned
1813 * (this may just be the empty string).
1814 * @param { Number } [x] The x-value of the selected points.
1815 * @param { [Object] } [sel_points] List of selected points for the given
1816 * x-value. Should have properties like 'name', 'yval' and 'canvasy'.
1818 Dygraph
.prototype.generateLegendHTML_
= function(x
, sel_points
) {
1819 // If no points are selected, we display a default legend. Traditionally,
1820 // this has been blank. But a better default would be a conventional legend,
1821 // which provides essential information for a non-interactive chart.
1822 if (typeof(x
) === 'undefined') {
1823 if (this.attr_('legend') != 'always') return '';
1825 var sepLines
= this.attr_('labelsSeparateLines');
1826 var labels
= this.attr_('labels');
1828 for (var i
= 1; i
< labels
.length
; i
++) {
1829 if (!this.visibility()[i
- 1]) continue;
1830 var c
= this.plotter_
.colors
[labels
[i
]];
1831 if (html
!= '') html
+= (sepLines
? '<br/>' : ' ');
1832 html
+= "<b><span style='color: " + c
+ ";'>—" + labels
[i
] +
1838 var html
= this.attr_('xValueFormatter')(x
) + ":";
1840 var fmtFunc
= this.attr_('yValueFormatter');
1841 var showZeros
= this.attr_("labelsShowZeroValues");
1842 var sepLines
= this.attr_("labelsSeparateLines");
1843 for (var i
= 0; i
< this.selPoints_
.length
; i
++) {
1844 var pt
= this.selPoints_
[i
];
1845 if (pt
.yval
== 0 && !showZeros
) continue;
1846 if (!Dygraph
.isOK(pt
.canvasy
)) continue;
1847 if (sepLines
) html
+= "<br/>";
1849 var c
= this.plotter_
.colors
[pt
.name
];
1850 var yval
= fmtFunc(pt
.yval
, this);
1851 // TODO(danvk): use a template string here and make it an attribute.
1852 html
+= " <b><span style='color: " + c
+ ";'>"
1853 + pt
.name
+ "</span></b>:"
1861 * Displays information about the selected points in the legend. If there is no
1862 * selection, the legend will be cleared.
1863 * @param { Number } [x] The x-value of the selected points.
1864 * @param { [Object] } [sel_points] List of selected points for the given
1865 * x-value. Should have properties like 'name', 'yval' and 'canvasy'.
1867 Dygraph
.prototype.setLegendHTML_
= function(x
, sel_points
) {
1868 var html
= this.generateLegendHTML_(x
, sel_points
);
1869 var labelsDiv
= this.attr_("labelsDiv");
1870 if (labelsDiv
!== null) {
1871 labelsDiv
.innerHTML
= html
;
1873 if (typeof(this.shown_legend_error_
) == 'undefined') {
1874 this.error('labelsDiv is set to something nonexistent; legend will not be shown.');
1875 this.shown_legend_error_
= true;
1881 * Draw dots over the selectied points in the data series. This function
1882 * takes care of cleanup of previously-drawn dots.
1885 Dygraph
.prototype.updateSelection_
= function() {
1886 // Clear the previously drawn vertical, if there is one
1887 var ctx
= this.canvas_ctx_
;
1888 if (this.previousVerticalX_
>= 0) {
1889 // Determine the maximum highlight circle size.
1890 var maxCircleSize
= 0;
1891 var labels
= this.attr_('labels');
1892 for (var i
= 1; i
< labels
.length
; i
++) {
1893 var r
= this.attr_('highlightCircleSize', labels
[i
]);
1894 if (r
> maxCircleSize
) maxCircleSize
= r
;
1896 var px
= this.previousVerticalX_
;
1897 ctx
.clearRect(px
- maxCircleSize
- 1, 0,
1898 2 * maxCircleSize
+ 2, this.height_
);
1901 if (this.selPoints_
.length
> 0) {
1902 // Set the status message to indicate the selected point(s)
1903 if (this.attr_('showLabelsOnHighlight')) {
1904 this.setLegendHTML_(this.lastx_
, this.selPoints_
);
1907 // Draw colored circles over the center of each selected point
1908 var canvasx
= this.selPoints_
[0].canvasx
;
1910 for (var i
= 0; i
< this.selPoints_
.length
; i
++) {
1911 var pt
= this.selPoints_
[i
];
1912 if (!Dygraph
.isOK(pt
.canvasy
)) continue;
1914 var circleSize
= this.attr_('highlightCircleSize', pt
.name
);
1916 ctx
.fillStyle
= this.plotter_
.colors
[pt
.name
];
1917 ctx
.arc(canvasx
, pt
.canvasy
, circleSize
, 0, 2 * Math
.PI
, false);
1922 this.previousVerticalX_
= canvasx
;
1927 * Manually set the selected points and display information about them in the
1928 * legend. The selection can be cleared using clearSelection() and queried
1929 * using getSelection().
1930 * @param { Integer } row number that should be highlighted (i.e. appear with
1931 * hover dots on the chart). Set to false to clear any selection.
1933 Dygraph
.prototype.setSelection
= function(row
) {
1934 // Extract the points we've selected
1935 this.selPoints_
= [];
1938 if (row
!== false) {
1939 row
= row
-this.boundaryIds_
[0][0];
1942 if (row
!== false && row
>= 0) {
1943 for (var i
in this.layout_
.datasets
) {
1944 if (row
< this.layout_
.datasets
[i
].length
) {
1945 var point
= this.layout_
.points
[pos
+row
];
1947 if (this.attr_("stackedGraph")) {
1948 point
= this.layout_
.unstackPointAtIndex(pos
+row
);
1951 this.selPoints_
.push(point
);
1953 pos
+= this.layout_
.datasets
[i
].length
;
1957 if (this.selPoints_
.length
) {
1958 this.lastx_
= this.selPoints_
[0].xval
;
1959 this.updateSelection_();
1961 this.clearSelection();
1967 * The mouse has left the canvas. Clear out whatever artifacts remain
1968 * @param {Object} event the mouseout event from the browser.
1971 Dygraph
.prototype.mouseOut_
= function(event
) {
1972 if (this.attr_("unhighlightCallback")) {
1973 this.attr_("unhighlightCallback")(event
);
1976 if (this.attr_("hideOverlayOnMouseOut")) {
1977 this.clearSelection();
1982 * Clears the current selection (i.e. points that were highlighted by moving
1983 * the mouse over the chart).
1985 Dygraph
.prototype.clearSelection
= function() {
1986 // Get rid of the overlay data
1987 this.canvas_ctx_
.clearRect(0, 0, this.width_
, this.height_
);
1988 this.setLegendHTML_();
1989 this.selPoints_
= [];
1994 * Returns the number of the currently selected row. To get data for this row,
1995 * you can use the getValue method.
1996 * @return { Integer } row number, or -1 if nothing is selected
1998 Dygraph
.prototype.getSelection
= function() {
1999 if (!this.selPoints_
|| this.selPoints_
.length
< 1) {
2003 for (var row
=0; row
<this.layout_
.points
.length
; row
++ ) {
2004 if (this.layout_
.points
[row
].x
== this.selPoints_
[0].x
) {
2005 return row
+ this.boundaryIds_
[0][0];
2012 * Number formatting function which mimicks the behavior of %g in printf, i.e.
2013 * either exponential or fixed format (without trailing 0s) is used depending on
2014 * the length of the generated string. The advantage of this format is that
2015 * there is a predictable upper bound on the resulting string length,
2016 * significant figures are not dropped, and normal numbers are not displayed in
2017 * exponential notation.
2019 * NOTE: JavaScript's native toPrecision() is NOT a drop-in replacement for %g.
2020 * It creates strings which are too long for absolute values between 10^-4 and
2021 * 10^-6, e.g. '0.00001' instead of '1e-5'. See tests/number-format.html for
2024 * @param {Number} x The number to format
2025 * @param {Number} opt_precision The precision to use, default 2.
2026 * @return {String} A string formatted like %g in printf. The max generated
2027 * string length should be precision + 6 (e.g 1.123e+300).
2029 Dygraph
.floatFormat
= function(x
, opt_precision
) {
2030 // Avoid invalid precision values; [1, 21] is the valid range.
2031 var p
= Math
.min(Math
.max(1, opt_precision
|| 2), 21);
2033 // This is deceptively simple. The actual algorithm comes from:
2035 // Max allowed length = p + 4
2036 // where 4 comes from 'e+n' and '.'.
2038 // Length of fixed format = 2 + y + p
2039 // where 2 comes from '0.' and y = # of leading zeroes.
2041 // Equating the two and solving for y yields y = 2, or 0.00xxxx which is
2044 // Since the behavior of toPrecision() is identical for larger numbers, we
2045 // don't have to worry about the other bound.
2047 // Finally, the argument for toExponential() is the number of trailing digits,
2048 // so we take off 1 for the value before the '.'.
2049 return (Math
.abs(x
) < 1.0e-3 && x
!= 0.0) ?
2050 x
.toExponential(p
- 1) : x
.toPrecision(p
);
2055 * Return a string version of a number. This respects the digitsAfterDecimal
2056 * and maxNumberWidth options.
2057 * @param {Number} x The number to be formatted
2058 * @param {Dygraph} g The dygraph object
2060 Dygraph
.numberFormatter
= function(x
, g
) {
2061 var sigFigs
= g
.attr_('sigFigs');
2063 if (sigFigs
!== null) {
2064 // User has opted for a fixed number of significant figures.
2065 return Dygraph
.floatFormat(x
, sigFigs
);
2068 var digits
= g
.attr_('digitsAfterDecimal');
2069 var maxNumberWidth
= g
.attr_('maxNumberWidth');
2071 // switch to scientific notation if we underflow or overflow fixed display.
2073 (Math
.abs(x
) >= Math
.pow(10, maxNumberWidth
) ||
2074 Math
.abs(x
) < Math
.pow(10, -digits
))) {
2075 return x
.toExponential(digits
);
2077 return '' + Dygraph
.round_(x
, digits
);
2083 * Converts '9' to '09' (useful for dates)
2085 Dygraph
.zeropad
= function(x
) {
2086 if (x
< 10) return "0" + x
; else return "" + x
;
2090 * Return a string version of the hours, minutes and seconds portion of a date.
2091 * @param {Number} date The JavaScript date (ms since epoch)
2092 * @return {String} A time of the form "HH:MM:SS"
2095 Dygraph
.hmsString_
= function(date
) {
2096 var zeropad
= Dygraph
.zeropad
;
2097 var d
= new Date(date
);
2098 if (d
.getSeconds()) {
2099 return zeropad(d
.getHours()) + ":" +
2100 zeropad(d
.getMinutes()) + ":" +
2101 zeropad(d
.getSeconds());
2103 return zeropad(d
.getHours()) + ":" + zeropad(d
.getMinutes());
2108 * Convert a JS date to a string appropriate to display on an axis that
2109 * is displaying values at the stated granularity.
2110 * @param {Date} date The date to format
2111 * @param {Number} granularity One of the Dygraph granularity constants
2112 * @return {String} The formatted date
2115 Dygraph
.dateAxisFormatter
= function(date
, granularity
) {
2116 if (granularity
>= Dygraph
.DECADAL
) {
2117 return date
.strftime('%Y');
2118 } else if (granularity
>= Dygraph
.MONTHLY
) {
2119 return date
.strftime('%b %y');
2121 var frac
= date
.getHours() * 3600 + date
.getMinutes() * 60 + date
.getSeconds() + date
.getMilliseconds();
2122 if (frac
== 0 || granularity
>= Dygraph
.DAILY
) {
2123 return new Date(date
.getTime() + 3600*1000).strftime('%d%b');
2125 return Dygraph
.hmsString_(date
.getTime());
2131 * Convert a JS date (millis since epoch) to YYYY/MM/DD
2132 * @param {Number} date The JavaScript date (ms since epoch)
2133 * @return {String} A date of the form "YYYY/MM/DD"
2136 Dygraph
.dateString_
= function(date
) {
2137 var zeropad
= Dygraph
.zeropad
;
2138 var d
= new Date(date
);
2141 var year
= "" + d
.getFullYear();
2142 // Get a 0 padded month string
2143 var month
= zeropad(d
.getMonth() + 1); //months are 0-offset, sigh
2144 // Get a 0 padded day string
2145 var day
= zeropad(d
.getDate());
2148 var frac
= d
.getHours() * 3600 + d
.getMinutes() * 60 + d
.getSeconds();
2149 if (frac
) ret
= " " + Dygraph
.hmsString_(date
);
2151 return year
+ "/" + month + "/" + day
+ ret
;
2155 * Round a number to the specified number of digits past the decimal point.
2156 * @param {Number} num The number to round
2157 * @param {Number} places The number of decimals to which to round
2158 * @return {Number} The rounded number
2161 Dygraph
.round_
= function(num
, places
) {
2162 var shift
= Math
.pow(10, places
);
2163 return Math
.round(num
* shift
)/shift
;
2167 * Fires when there's data available to be graphed.
2168 * @param {String} data Raw CSV data to be plotted
2171 Dygraph
.prototype.loadedEvent_
= function(data
) {
2172 this.rawData_
= this.parseCSV_(data
);
2176 Dygraph
.prototype.months
= ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
2177 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
2178 Dygraph
.prototype.quarters
= ["Jan", "Apr", "Jul", "Oct"];
2181 * Add ticks on the x-axis representing years, months, quarters, weeks, or days
2184 Dygraph
.prototype.addXTicks_
= function() {
2185 // Determine the correct ticks scale on the x-axis: quarterly, monthly, ...
2187 if (this.dateWindow_
) {
2188 range
= [this.dateWindow_
[0], this.dateWindow_
[1]];
2190 range
= [this.rawData_
[0][0], this.rawData_
[this.rawData_
.length
- 1][0]];
2193 var xTicks
= this.attr_('xTicker')(range
[0], range
[1], this);
2194 this.layout_
.setXTicks(xTicks
);
2197 // Time granularity enumeration
2198 Dygraph
.SECONDLY
= 0;
2199 Dygraph
.TWO_SECONDLY
= 1;
2200 Dygraph
.FIVE_SECONDLY
= 2;
2201 Dygraph
.TEN_SECONDLY
= 3;
2202 Dygraph
.THIRTY_SECONDLY
= 4;
2203 Dygraph
.MINUTELY
= 5;
2204 Dygraph
.TWO_MINUTELY
= 6;
2205 Dygraph
.FIVE_MINUTELY
= 7;
2206 Dygraph
.TEN_MINUTELY
= 8;
2207 Dygraph
.THIRTY_MINUTELY
= 9;
2208 Dygraph
.HOURLY
= 10;
2209 Dygraph
.TWO_HOURLY
= 11;
2210 Dygraph
.SIX_HOURLY
= 12;
2212 Dygraph
.WEEKLY
= 14;
2213 Dygraph
.MONTHLY
= 15;
2214 Dygraph
.QUARTERLY
= 16;
2215 Dygraph
.BIANNUAL
= 17;
2216 Dygraph
.ANNUAL
= 18;
2217 Dygraph
.DECADAL
= 19;
2218 Dygraph
.CENTENNIAL
= 20;
2219 Dygraph
.NUM_GRANULARITIES
= 21;
2221 Dygraph
.SHORT_SPACINGS
= [];
2222 Dygraph
.SHORT_SPACINGS
[Dygraph
.SECONDLY
] = 1000 * 1;
2223 Dygraph
.SHORT_SPACINGS
[Dygraph
.TWO_SECONDLY
] = 1000 * 2;
2224 Dygraph
.SHORT_SPACINGS
[Dygraph
.FIVE_SECONDLY
] = 1000 * 5;
2225 Dygraph
.SHORT_SPACINGS
[Dygraph
.TEN_SECONDLY
] = 1000 * 10;
2226 Dygraph
.SHORT_SPACINGS
[Dygraph
.THIRTY_SECONDLY
] = 1000 * 30;
2227 Dygraph
.SHORT_SPACINGS
[Dygraph
.MINUTELY
] = 1000 * 60;
2228 Dygraph
.SHORT_SPACINGS
[Dygraph
.TWO_MINUTELY
] = 1000 * 60 * 2;
2229 Dygraph
.SHORT_SPACINGS
[Dygraph
.FIVE_MINUTELY
] = 1000 * 60 * 5;
2230 Dygraph
.SHORT_SPACINGS
[Dygraph
.TEN_MINUTELY
] = 1000 * 60 * 10;
2231 Dygraph
.SHORT_SPACINGS
[Dygraph
.THIRTY_MINUTELY
] = 1000 * 60 * 30;
2232 Dygraph
.SHORT_SPACINGS
[Dygraph
.HOURLY
] = 1000 * 3600;
2233 Dygraph
.SHORT_SPACINGS
[Dygraph
.TWO_HOURLY
] = 1000 * 3600 * 2;
2234 Dygraph
.SHORT_SPACINGS
[Dygraph
.SIX_HOURLY
] = 1000 * 3600 * 6;
2235 Dygraph
.SHORT_SPACINGS
[Dygraph
.DAILY
] = 1000 * 86400;
2236 Dygraph
.SHORT_SPACINGS
[Dygraph
.WEEKLY
] = 1000 * 604800;
2240 * If we used this time granularity, how many ticks would there be?
2241 * This is only an approximation, but it's generally good enough.
2243 Dygraph
.prototype.NumXTicks
= function(start_time
, end_time
, granularity
) {
2244 if (granularity
< Dygraph
.MONTHLY
) {
2245 // Generate one tick mark for every fixed interval of time.
2246 var spacing
= Dygraph
.SHORT_SPACINGS
[granularity
];
2247 return Math
.floor(0.5 + 1.0 * (end_time
- start_time
) / spacing
);
2249 var year_mod
= 1; // e.g. to only print one point every 10 years.
2250 var num_months
= 12;
2251 if (granularity
== Dygraph
.QUARTERLY
) num_months
= 3;
2252 if (granularity
== Dygraph
.BIANNUAL
) num_months
= 2;
2253 if (granularity
== Dygraph
.ANNUAL
) num_months
= 1;
2254 if (granularity
== Dygraph
.DECADAL
) { num_months
= 1; year_mod
= 10; }
2255 if (granularity
== Dygraph
.CENTENNIAL
) { num_months
= 1; year_mod
= 100; }
2257 var msInYear
= 365.2524 * 24 * 3600 * 1000;
2258 var num_years
= 1.0 * (end_time
- start_time
) / msInYear
;
2259 return Math
.floor(0.5 + 1.0 * num_years
* num_months
/ year_mod
);
2266 * Construct an x-axis of nicely-formatted times on meaningful boundaries
2267 * (e.g. 'Jan 09' rather than 'Jan 22, 2009').
2269 * Returns an array containing {v: millis, label: label} dictionaries.
2271 Dygraph
.prototype.GetXAxis
= function(start_time
, end_time
, granularity
) {
2272 var formatter
= this.attr_("xAxisLabelFormatter");
2274 if (granularity
< Dygraph
.MONTHLY
) {
2275 // Generate one tick mark for every fixed interval of time.
2276 var spacing
= Dygraph
.SHORT_SPACINGS
[granularity
];
2277 var format
= '%d%b'; // e.g. "1Jan"
2279 // Find a time less than start_time which occurs on a "nice" time boundary
2280 // for this granularity.
2281 var g
= spacing
/ 1000;
2282 var d
= new Date(start_time
);
2283 if (g
<= 60) { // seconds
2284 var x
= d
.getSeconds(); d
.setSeconds(x
- x
% g
);
2288 if (g
<= 60) { // minutes
2289 var x
= d
.getMinutes(); d
.setMinutes(x
- x
% g
);
2294 if (g
<= 24) { // days
2295 var x
= d
.getHours(); d
.setHours(x
- x
% g
);
2300 if (g
== 7) { // one week
2301 d
.setDate(d
.getDate() - d
.getDay());
2306 start_time
= d
.getTime();
2308 for (var t
= start_time
; t
<= end_time
; t
+= spacing
) {
2309 ticks
.push({ v
:t
, label
: formatter(new Date(t
), granularity
) });
2312 // Display a tick mark on the first of a set of months of each year.
2313 // Years get a tick mark iff y % year_mod == 0. This is useful for
2314 // displaying a tick mark once every 10 years, say, on long time scales.
2316 var year_mod
= 1; // e.g. to only print one point every 10 years.
2318 if (granularity
== Dygraph
.MONTHLY
) {
2319 months
= [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ];
2320 } else if (granularity
== Dygraph
.QUARTERLY
) {
2321 months
= [ 0, 3, 6, 9 ];
2322 } else if (granularity
== Dygraph
.BIANNUAL
) {
2324 } else if (granularity
== Dygraph
.ANNUAL
) {
2326 } else if (granularity
== Dygraph
.DECADAL
) {
2329 } else if (granularity
== Dygraph
.CENTENNIAL
) {
2333 this.warn("Span of dates is too long");
2336 var start_year
= new Date(start_time
).getFullYear();
2337 var end_year
= new Date(end_time
).getFullYear();
2338 var zeropad
= Dygraph
.zeropad
;
2339 for (var i
= start_year
; i
<= end_year
; i
++) {
2340 if (i
% year_mod
!= 0) continue;
2341 for (var j
= 0; j
< months
.length
; j
++) {
2342 var date_str
= i
+ "/" + zeropad(1 + months[j]) + "/01";
2343 var t
= Dygraph
.dateStrToMillis(date_str
);
2344 if (t
< start_time
|| t
> end_time
) continue;
2345 ticks
.push({ v
:t
, label
: formatter(new Date(t
), granularity
) });
2355 * Add ticks to the x-axis based on a date range.
2356 * @param {Number} startDate Start of the date window (millis since epoch)
2357 * @param {Number} endDate End of the date window (millis since epoch)
2358 * @param {Dygraph} self The dygraph object
2359 * @return { [Object] } Array of {label, value} tuples.
2362 Dygraph
.dateTicker
= function(startDate
, endDate
, self
) {
2363 // TODO(danvk): why does this take 'self' as a param?
2365 for (var i
= 0; i
< Dygraph
.NUM_GRANULARITIES
; i
++) {
2366 var num_ticks
= self
.NumXTicks(startDate
, endDate
, i
);
2367 if (self
.width_
/ num_ticks
>= self
.attr_('pixelsPerXLabel')) {
2374 return self
.GetXAxis(startDate
, endDate
, chosen
);
2376 // TODO(danvk): signal error.
2382 * This is a list of human-friendly values at which to show tick marks on a log
2383 * scale. It is k * 10^n, where k=1..9 and n=-39..+39, so:
2384 * ..., 1, 2, 3, 4, 5, ..., 9, 10, 20, 30, ..., 90, 100, 200, 300, ...
2385 * NOTE: this assumes that Dygraph.LOG_SCALE = 10.
2387 Dygraph
.PREFERRED_LOG_TICK_VALUES
= function() {
2389 for (var power
= -39; power
<= 39; power
++) {
2390 var range
= Math
.pow(10, power
);
2391 for (var mult
= 1; mult
<= 9; mult
++) {
2392 var val
= range
* mult
;
2401 * Implementation of binary search over an array.
2402 * Currently does not work when val is outside the range of arry's values.
2403 * @param { Integer } val the value to search for
2404 * @param { Integer[] } arry is the value over which to search
2405 * @param { Integer } abs If abs > 0, find the lowest entry greater than val
2406 * If abs < 0, find the highest entry less than val.
2407 * if abs == 0, find the entry that equals val.
2408 * @param { Integer } [low] The first index in arry to consider (optional)
2409 * @param { Integer } [high] The last index in arry to consider (optional)
2411 Dygraph
.binarySearch
= function(val
, arry
, abs
, low
, high
) {
2412 if (low
== null || high
== null) {
2414 high
= arry
.length
- 1;
2422 var validIndex
= function(idx
) {
2423 return idx
>= 0 && idx
< arry
.length
;
2425 var mid
= parseInt((low
+ high
) / 2);
2426 var element
= arry
[mid
];
2427 if (element
== val
) {
2430 if (element
> val
) {
2432 // Accept if element > val, but also if prior element < val.
2434 if (validIndex(idx
) && arry
[idx
] < val
) {
2438 return Dygraph
.binarySearch(val
, arry
, abs
, low
, mid
- 1);
2440 if (element
< val
) {
2442 // Accept if element < val, but also if prior element > val.
2444 if (validIndex(idx
) && arry
[idx
] > val
) {
2448 return Dygraph
.binarySearch(val
, arry
, abs
, mid
+ 1, high
);
2452 // TODO(konigsberg): Update comment.
2454 * Add ticks when the x axis has numbers on it (instead of dates)
2456 * @param {Number} minV minimum value
2457 * @param {Number} maxV maximum value
2459 * @param {function} attribute accessor function.
2460 * @return {[Object]} Array of {label, value} tuples.
2462 Dygraph
.numericTicks
= function(minV
, maxV
, self
, axis_props
, vals
) {
2463 var attr
= function(k
) {
2464 if (axis_props
&& axis_props
.hasOwnProperty(k
)) return axis_props
[k
];
2465 return self
.attr_(k
);
2470 for (var i
= 0; i
< vals
.length
; i
++) {
2471 ticks
.push({v
: vals
[i
]});
2474 if (axis_props
&& attr("logscale")) {
2475 var pixelsPerTick
= attr('pixelsPerYLabel');
2476 // NOTE(konigsberg): Dan, should self.height_ be self.plotter_.area.h?
2477 var nTicks
= Math
.floor(self
.height_
/ pixelsPerTick
);
2478 var minIdx
= Dygraph
.binarySearch(minV
, Dygraph
.PREFERRED_LOG_TICK_VALUES
, 1);
2479 var maxIdx
= Dygraph
.binarySearch(maxV
, Dygraph
.PREFERRED_LOG_TICK_VALUES
, -1);
2484 maxIdx
= Dygraph
.PREFERRED_LOG_TICK_VALUES
.length
- 1;
2486 // Count the number of tick values would appear, if we can get at least
2487 // nTicks / 4 accept them
.
2488 var lastDisplayed
= null;
2489 if (maxIdx
- minIdx
>= nTicks
/ 4) {
2490 var axisId
= axis_props
.yAxisId
;
2491 for (var idx
= maxIdx
; idx
>= minIdx
; idx
--) {
2492 var tickValue
= Dygraph
.PREFERRED_LOG_TICK_VALUES
[idx
];
2493 var domCoord
= axis_props
.g
.toDomYCoord(tickValue
, axisId
);
2494 var tick
= { v
: tickValue
};
2495 if (lastDisplayed
== null) {
2497 tickValue
: tickValue
,
2501 if (domCoord
- lastDisplayed
.domCoord
>= pixelsPerTick
) {
2503 tickValue
: tickValue
,
2512 // Since we went in backwards order.
2517 // ticks.length won't be 0 if the log scale function finds values to insert.
2518 if (ticks
.length
== 0) {
2520 // Try labels every 1, 2, 5, 10, 20, 50, 100, etc.
2521 // Calculate the resulting tick spacing (i.e. this.height_ / nTicks
).
2522 // The first spacing greater than pixelsPerYLabel is what we use.
2523 // TODO(danvk): version that works on a log scale.
2524 if (attr("labelsKMG2")) {
2525 var mults
= [1, 2, 4, 8];
2527 var mults
= [1, 2, 5];
2529 var scale
, low_val
, high_val
, nTicks
;
2530 // TODO(danvk): make it possible to set this for x- and y-axes independently.
2531 var pixelsPerTick
= attr('pixelsPerYLabel');
2532 for (var i
= -10; i
< 50; i
++) {
2533 if (attr("labelsKMG2")) {
2534 var base_scale
= Math
.pow(16, i
);
2536 var base_scale
= Math
.pow(10, i
);
2538 for (var j
= 0; j
< mults
.length
; j
++) {
2539 scale
= base_scale
* mults
[j
];
2540 low_val
= Math
.floor(minV
/ scale
) * scale
;
2541 high_val
= Math
.ceil(maxV
/ scale
) * scale
;
2542 nTicks
= Math
.abs(high_val
- low_val
) / scale
;
2543 var spacing
= self
.height_
/ nTicks
;
2544 // wish I could break out of both loops at once...
2545 if (spacing
> pixelsPerTick
) break;
2547 if (spacing
> pixelsPerTick
) break;
2550 // Construct the set of ticks.
2551 // Allow reverse y-axis if it's explicitly requested.
2552 if (low_val
> high_val
) scale
*= -1;
2553 for (var i
= 0; i
< nTicks
; i
++) {
2554 var tickV
= low_val
+ i
* scale
;
2555 ticks
.push( {v
: tickV
} );
2560 // Add formatted labels to the ticks.
2563 if (attr("labelsKMB")) {
2565 k_labels
= [ "K", "M", "B", "T" ];
2567 if (attr("labelsKMG2")) {
2568 if (k
) self
.warn("Setting both labelsKMB and labelsKMG2. Pick one!");
2570 k_labels
= [ "k", "M", "G", "T" ];
2572 var formatter
= attr('yAxisLabelFormatter') ?
2573 attr('yAxisLabelFormatter') : attr('yValueFormatter');
2575 // Add labels to the ticks.
2576 for (var i
= 0; i
< ticks
.length
; i
++) {
2577 if (ticks
[i
].label
!== undefined
) continue; // Use current label.
2578 var tickV
= ticks
[i
].v
;
2579 var absTickV
= Math
.abs(tickV
);
2580 var label
= formatter(tickV
, self
);
2581 if (k_labels
.length
> 0) {
2582 // Round up to an appropriate unit.
2584 for (var j
= 3; j
>= 0; j
--, n
/= k
) {
2585 if (absTickV
>= n
) {
2586 label
= Dygraph
.round_(tickV
/ n
, attr('digitsAfterDecimal')) + k_labels
[j
];
2591 ticks
[i
].label
= label
;
2599 * Computes the range of the data series (including confidence intervals).
2600 * @param { [Array] } series either [ [x1, y1], [x2, y2], ... ] or
2601 * [ [x1, [y1, dev_low, dev_high]], [x2, [y2, dev_low, dev_high]], ...
2602 * @return [low, high]
2604 Dygraph
.prototype.extremeValues_
= function(series
) {
2605 var minY
= null, maxY
= null;
2607 var bars
= this.attr_("errorBars") || this.attr_("customBars");
2609 // With custom bars, maxY is the max of the high values.
2610 for (var j
= 0; j
< series
.length
; j
++) {
2611 var y
= series
[j
][1][0];
2613 var low
= y
- series
[j
][1][1];
2614 var high
= y
+ series
[j
][1][2];
2615 if (low
> y
) low
= y
; // this can happen with custom bars,
2616 if (high
< y
) high
= y
; // e.g. in tests/custom-bars
.html
2617 if (maxY
== null || high
> maxY
) {
2620 if (minY
== null || low
< minY
) {
2625 for (var j
= 0; j
< series
.length
; j
++) {
2626 var y
= series
[j
][1];
2627 if (y
=== null || isNaN(y
)) continue;
2628 if (maxY
== null || y
> maxY
) {
2631 if (minY
== null || y
< minY
) {
2637 return [minY
, maxY
];
2642 * This function is called once when the chart's data is changed or the options
2643 * dictionary is updated. It is _not_ called when the user pans or zooms. The
2644 * idea is that values derived from the chart's data can be computed here,
2645 * rather than every time the chart is drawn. This includes things like the
2646 * number of axes, rolling averages, etc.
2648 Dygraph
.prototype.predraw_
= function() {
2649 // TODO(danvk): move more computations out of drawGraph_ and into here.
2650 this.computeYAxes_();
2652 // Create a new plotter.
2653 if (this.plotter_
) this.plotter_
.clear();
2654 this.plotter_
= new DygraphCanvasRenderer(this,
2658 this.renderOptions_
);
2660 // The roller sits in the bottom left corner of the chart. We don't know where
2661 // this will be until the options are available, so it's positioned here.
2662 this.createRollInterface_();
2664 // Same thing applies for the labelsDiv. It's right edge should be flush with
2665 // the right edge of the charting area (which may not be the same as the right
2666 // edge of the div, if we have two y-axes.
2667 this.positionLabelsDiv_();
2669 // If the data or options have changed, then we'd better redraw.
2674 * Update the graph with new data. This method is called when the viewing area
2675 * has changed. If the underlying data or options have changed, predraw_ will
2676 * be called before drawGraph_ is called.
2679 Dygraph
.prototype.drawGraph_
= function() {
2680 var data
= this.rawData_
;
2682 // This is used to set the second parameter to drawCallback, below.
2683 var is_initial_draw
= this.is_initial_draw_
;
2684 this.is_initial_draw_
= false;
2686 var minY
= null, maxY
= null;
2687 this.layout_
.removeAllDatasets();
2689 this.attrs_
['pointSize'] = 0.5 * this.attr_('highlightCircleSize');
2691 // Loop over the fields (series). Go from the last to the first,
2692 // because if they're stacked that's how we accumulate the values.
2694 var cumulative_y
= []; // For stacked series.
2697 var extremes
= {}; // series name -> [low, high]
2699 // Loop over all fields and create datasets
2700 for (var i
= data
[0].length
- 1; i
>= 1; i
--) {
2701 if (!this.visibility()[i
- 1]) continue;
2703 var seriesName
= this.attr_("labels")[i
];
2704 var connectSeparatedPoints
= this.attr_('connectSeparatedPoints', i
);
2705 var logScale
= this.attr_('logscale', i
);
2708 for (var j
= 0; j
< data
.length
; j
++) {
2709 var date
= data
[j
][0];
2710 var point
= data
[j
][i
];
2712 // On the log scale, points less than zero do not exist.
2713 // This will create a gap in the chart. Note that this ignores
2714 // connectSeparatedPoints.
2718 series
.push([date
, point
]);
2720 if (point
!= null || !connectSeparatedPoints
) {
2721 series
.push([date
, point
]);
2726 // TODO(danvk): move this into predraw_. It's insane to do it here.
2727 series
= this.rollingAverage(series
, this.rollPeriod_
);
2729 // Prune down to the desired range, if necessary (for zooming)
2730 // Because there can be lines going to points outside of the visible area,
2731 // we actually prune to visible points, plus one on either side.
2732 var bars
= this.attr_("errorBars") || this.attr_("customBars");
2733 if (this.dateWindow_
) {
2734 var low
= this.dateWindow_
[0];
2735 var high
= this.dateWindow_
[1];
2737 // TODO(danvk): do binary search instead of linear search.
2738 // TODO(danvk): pass firstIdx and lastIdx directly to the renderer.
2739 var firstIdx
= null, lastIdx
= null;
2740 for (var k
= 0; k
< series
.length
; k
++) {
2741 if (series
[k
][0] >= low
&& firstIdx
=== null) {
2744 if (series
[k
][0] <= high
) {
2748 if (firstIdx
=== null) firstIdx
= 0;
2749 if (firstIdx
> 0) firstIdx
--;
2750 if (lastIdx
=== null) lastIdx
= series
.length
- 1;
2751 if (lastIdx
< series
.length
- 1) lastIdx
++;
2752 this.boundaryIds_
[i
-1] = [firstIdx
, lastIdx
];
2753 for (var k
= firstIdx
; k
<= lastIdx
; k
++) {
2754 pruned
.push(series
[k
]);
2758 this.boundaryIds_
[i
-1] = [0, series
.length
-1];
2761 var seriesExtremes
= this.extremeValues_(series
);
2764 for (var j
=0; j
<series
.length
; j
++) {
2765 val
= [series
[j
][0], series
[j
][1][0], series
[j
][1][1], series
[j
][1][2]];
2768 } else if (this.attr_("stackedGraph")) {
2769 var l
= series
.length
;
2771 for (var j
= 0; j
< l
; j
++) {
2772 // If one data set has a NaN, let all subsequent stacked
2773 // sets inherit the NaN -- only start at 0 for the first set.
2774 var x
= series
[j
][0];
2775 if (cumulative_y
[x
] === undefined
) {
2776 cumulative_y
[x
] = 0;
2779 actual_y
= series
[j
][1];
2780 cumulative_y
[x
] += actual_y
;
2782 series
[j
] = [x
, cumulative_y
[x
]]
2784 if (cumulative_y
[x
] > seriesExtremes
[1]) {
2785 seriesExtremes
[1] = cumulative_y
[x
];
2787 if (cumulative_y
[x
] < seriesExtremes
[0]) {
2788 seriesExtremes
[0] = cumulative_y
[x
];
2792 extremes
[seriesName
] = seriesExtremes
;
2794 datasets
[i
] = series
;
2797 for (var i
= 1; i
< datasets
.length
; i
++) {
2798 if (!this.visibility()[i
- 1]) continue;
2799 this.layout_
.addDataset(this.attr_("labels")[i
], datasets
[i
]);
2802 this.computeYAxisRanges_(extremes
);
2803 this.layout_
.setYAxes(this.axes_
);
2807 // Save the X axis zoomed status as the updateOptions call will tend to set it erroneously
2808 var tmp_zoomed_x
= this.zoomed_x_
;
2809 // Tell PlotKit to use this new data and render itself
2810 this.layout_
.setDateWindow(this.dateWindow_
);
2811 this.zoomed_x_
= tmp_zoomed_x
;
2812 this.layout_
.evaluateWithError();
2813 this.plotter_
.clear();
2814 this.plotter_
.render();
2815 this.canvas_
.getContext('2d').clearRect(0, 0, this.canvas_
.width
,
2816 this.canvas_
.height
);
2818 if (is_initial_draw
) {
2819 // Generate a static legend before any particular point is selected.
2820 this.setLegendHTML_();
2822 if (typeof(this.selPoints_
) !== 'undefined' && this.selPoints_
.length
) {
2823 this.lastx_
= this.selPoints_
[0].xval
;
2824 this.updateSelection_();
2826 this.clearSelection();
2830 if (this.attr_("drawCallback") !== null) {
2831 this.attr_("drawCallback")(this, is_initial_draw
);
2837 * Determine properties of the y-axes which are independent of the data
2838 * currently being displayed. This includes things like the number of axes and
2839 * the style of the axes. It does not include the range of each axis and its
2841 * This fills in this.axes_ and this.seriesToAxisMap_.
2842 * axes_ = [ { options } ]
2843 * seriesToAxisMap_ = { seriesName: 0, seriesName2: 1, ... }
2844 * indices are into the axes_ array.
2846 Dygraph
.prototype.computeYAxes_
= function() {
2847 this.axes_
= [{ yAxisId
: 0, g
: this }]; // always have at least one y-axis.
2848 this.seriesToAxisMap_
= {};
2850 // Get a list of series names.
2851 var labels
= this.attr_("labels");
2853 for (var i
= 1; i
< labels
.length
; i
++) series
[labels
[i
]] = (i
- 1);
2855 // all options which could be applied per-axis:
2863 'axisLabelFontSize',
2868 // Copy global axis options over to the first axis.
2869 for (var i
= 0; i
< axisOptions
.length
; i
++) {
2870 var k
= axisOptions
[i
];
2871 var v
= this.attr_(k
);
2872 if (v
) this.axes_
[0][k
] = v
;
2875 // Go through once and add all the axes.
2876 for (var seriesName
in series
) {
2877 if (!series
.hasOwnProperty(seriesName
)) continue;
2878 var axis
= this.attr_("axis", seriesName
);
2880 this.seriesToAxisMap_
[seriesName
] = 0;
2883 if (typeof(axis
) == 'object') {
2884 // Add a new axis, making a copy of its per-axis options.
2886 Dygraph
.update(opts
, this.axes_
[0]);
2887 Dygraph
.update(opts
, { valueRange
: null }); // shouldn't inherit this.
2888 var yAxisId
= this.axes_
.length
;
2889 opts
.yAxisId
= yAxisId
;
2891 Dygraph
.update(opts
, axis
);
2892 this.axes_
.push(opts
);
2893 this.seriesToAxisMap_
[seriesName
] = yAxisId
;
2897 // Go through one more time and assign series to an axis defined by another
2898 // series, e.g. { 'Y1: { axis: {} }, 'Y2': { axis: 'Y1' } }
2899 for (var seriesName
in series
) {
2900 if (!series
.hasOwnProperty(seriesName
)) continue;
2901 var axis
= this.attr_("axis", seriesName
);
2902 if (typeof(axis
) == 'string') {
2903 if (!this.seriesToAxisMap_
.hasOwnProperty(axis
)) {
2904 this.error("Series " + seriesName
+ " wants to share a y-axis with " +
2905 "series " + axis
+ ", which does not define its own axis.");
2908 var idx
= this.seriesToAxisMap_
[axis
];
2909 this.seriesToAxisMap_
[seriesName
] = idx
;
2913 // Now we remove series from seriesToAxisMap_ which are not visible. We do
2914 // this last so that hiding the first series doesn't destroy the axis
2915 // properties of the primary axis.
2916 var seriesToAxisFiltered
= {};
2917 var vis
= this.visibility();
2918 for (var i
= 1; i
< labels
.length
; i
++) {
2920 if (vis
[i
- 1]) seriesToAxisFiltered
[s
] = this.seriesToAxisMap_
[s
];
2922 this.seriesToAxisMap_
= seriesToAxisFiltered
;
2926 * Returns the number of y-axes on the chart.
2927 * @return {Number} the number of axes.
2929 Dygraph
.prototype.numAxes
= function() {
2931 for (var series
in this.seriesToAxisMap_
) {
2932 if (!this.seriesToAxisMap_
.hasOwnProperty(series
)) continue;
2933 var idx
= this.seriesToAxisMap_
[series
];
2934 if (idx
> last_axis
) last_axis
= idx
;
2936 return 1 + last_axis
;
2941 * Returns axis properties for the given series.
2942 * @param { String } setName The name of the series for which to get axis
2943 * properties, e.g. 'Y1'.
2944 * @return { Object } The axis properties.
2946 Dygraph
.prototype.axisPropertiesForSeries
= function(series
) {
2947 // TODO(danvk): handle errors.
2948 return this.axes_
[this.seriesToAxisMap_
[series
]];
2953 * Determine the value range and tick marks for each axis.
2954 * @param {Object} extremes A mapping from seriesName -> [low, high]
2955 * This fills in the valueRange and ticks fields in each entry of this.axes_.
2957 Dygraph
.prototype.computeYAxisRanges_
= function(extremes
) {
2958 // Build a map from axis number -> [list of series names]
2959 var seriesForAxis
= [];
2960 for (var series
in this.seriesToAxisMap_
) {
2961 if (!this.seriesToAxisMap_
.hasOwnProperty(series
)) continue;
2962 var idx
= this.seriesToAxisMap_
[series
];
2963 while (seriesForAxis
.length
<= idx
) seriesForAxis
.push([]);
2964 seriesForAxis
[idx
].push(series
);
2967 // Compute extreme values, a span and tick marks for each axis.
2968 for (var i
= 0; i
< this.axes_
.length
; i
++) {
2969 var axis
= this.axes_
[i
];
2971 if (!seriesForAxis
[i
]) {
2972 // If no series are defined or visible then use a reasonable default
2973 axis
.extremeRange
= [0, 1];
2975 // Calculate the extremes of extremes.
2976 var series
= seriesForAxis
[i
];
2977 var minY
= Infinity
; // extremes[series[0]][0];
2978 var maxY
= -Infinity
; // extremes[series[0]][1];
2979 var extremeMinY
, extremeMaxY
;
2980 for (var j
= 0; j
< series
.length
; j
++) {
2981 // Only use valid extremes to stop null data series' from corrupting the scale.
2982 extremeMinY
= extremes
[series
[j
]][0];
2983 if (extremeMinY
!= null) {
2984 minY
= Math
.min(extremeMinY
, minY
);
2986 extremeMaxY
= extremes
[series
[j
]][1];
2987 if (extremeMaxY
!= null) {
2988 maxY
= Math
.max(extremeMaxY
, maxY
);
2991 if (axis
.includeZero
&& minY
> 0) minY
= 0;
2993 // Ensure we have a valid scale, otherwise defualt to zero for safety.
2994 if (minY
== Infinity
) minY
= 0;
2995 if (maxY
== -Infinity
) maxY
= 0;
2997 // Add some padding and round up to an integer to be human-friendly.
2998 var span
= maxY
- minY
;
2999 // special case: if we have no sense of scale, use +/-10% of the sole value
.
3000 if (span
== 0) { span
= maxY
; }
3004 if (axis
.logscale
) {
3005 var maxAxisY
= maxY
+ 0.1 * span
;
3006 var minAxisY
= minY
;
3008 var maxAxisY
= maxY
+ 0.1 * span
;
3009 var minAxisY
= minY
- 0.1 * span
;
3011 // Try to include zero and make it minAxisY (or maxAxisY) if it makes sense.
3012 if (!this.attr_("avoidMinZero")) {
3013 if (minAxisY
< 0 && minY
>= 0) minAxisY
= 0;
3014 if (maxAxisY
> 0 && maxY
<= 0) maxAxisY
= 0;
3017 if (this.attr_("includeZero")) {
3018 if (maxY
< 0) maxAxisY
= 0;
3019 if (minY
> 0) minAxisY
= 0;
3022 axis
.extremeRange
= [minAxisY
, maxAxisY
];
3024 if (axis
.valueWindow
) {
3025 // This is only set if the user has zoomed on the y-axis. It is never set
3026 // by a user. It takes precedence over axis.valueRange because, if you set
3027 // valueRange, you'd still expect to be able to pan.
3028 axis
.computedValueRange
= [axis
.valueWindow
[0], axis
.valueWindow
[1]];
3029 } else if (axis
.valueRange
) {
3030 // This is a user-set value range for this axis.
3031 axis
.computedValueRange
= [axis
.valueRange
[0], axis
.valueRange
[1]];
3033 axis
.computedValueRange
= axis
.extremeRange
;
3036 // Add ticks. By default, all axes inherit the tick positions of the
3037 // primary axis. However, if an axis is specifically marked as having
3038 // independent ticks, then that is permissible as well.
3039 if (i
== 0 || axis
.independentTicks
) {
3041 Dygraph
.numericTicks(axis
.computedValueRange
[0],
3042 axis
.computedValueRange
[1],
3046 var p_axis
= this.axes_
[0];
3047 var p_ticks
= p_axis
.ticks
;
3048 var p_scale
= p_axis
.computedValueRange
[1] - p_axis
.computedValueRange
[0];
3049 var scale
= axis
.computedValueRange
[1] - axis
.computedValueRange
[0];
3050 var tick_values
= [];
3051 for (var i
= 0; i
< p_ticks
.length
; i
++) {
3052 var y_frac
= (p_ticks
[i
].v
- p_axis
.computedValueRange
[0]) / p_scale
;
3053 var y_val
= axis
.computedValueRange
[0] + y_frac
* scale
;
3054 tick_values
.push(y_val
);
3058 Dygraph
.numericTicks(axis
.computedValueRange
[0],
3059 axis
.computedValueRange
[1],
3060 this, axis
, tick_values
);
3067 * Calculates the rolling average of a data set.
3068 * If originalData is [label, val], rolls the average of those.
3069 * If originalData is [label, [, it's interpreted as [value, stddev]
3070 * and the roll is returned in the same form, with appropriately reduced
3071 * stddev for each value.
3072 * Note that this is where fractional input (i.e. '5/10') is converted into
3074 * @param {Array} originalData The data in the appropriate format (see above)
3075 * @param {Number} rollPeriod The number of points over which to average the
3078 Dygraph
.prototype.rollingAverage
= function(originalData
, rollPeriod
) {
3079 if (originalData
.length
< 2)
3080 return originalData
;
3081 var rollPeriod
= Math
.min(rollPeriod
, originalData
.length
- 1);
3082 var rollingData
= [];
3083 var sigma
= this.attr_("sigma");
3085 if (this.fractions_
) {
3087 var den
= 0; // numerator/denominator
3089 for (var i
= 0; i
< originalData
.length
; i
++) {
3090 num
+= originalData
[i
][1][0];
3091 den
+= originalData
[i
][1][1];
3092 if (i
- rollPeriod
>= 0) {
3093 num
-= originalData
[i
- rollPeriod
][1][0];
3094 den
-= originalData
[i
- rollPeriod
][1][1];
3097 var date
= originalData
[i
][0];
3098 var value
= den
? num
/ den
: 0.0;
3099 if (this.attr_("errorBars")) {
3100 if (this.wilsonInterval_
) {
3101 // For more details on this confidence interval, see:
3102 // http://en.wikipedia.org/wiki
/Binomial_confidence_interval
3104 var p
= value
< 0 ? 0 : value
, n
= den
;
3105 var pm
= sigma
* Math
.sqrt(p
*(1-p
)/n + sigma*sigma/(4*n
*n
));
3106 var denom
= 1 + sigma
* sigma
/ den
;
3107 var low
= (p
+ sigma
* sigma
/ (2 * den) - pm) / denom
;
3108 var high
= (p
+ sigma
* sigma
/ (2 * den) + pm) / denom
;
3109 rollingData
[i
] = [date
,
3110 [p
* mult
, (p
- low
) * mult
, (high
- p
) * mult
]];
3112 rollingData
[i
] = [date
, [0, 0, 0]];
3115 var stddev
= den
? sigma
* Math
.sqrt(value
* (1 - value
) / den
) : 1.0;
3116 rollingData
[i
] = [date
, [mult
* value
, mult
* stddev
, mult
* stddev
]];
3119 rollingData
[i
] = [date
, mult
* value
];
3122 } else if (this.attr_("customBars")) {
3127 for (var i
= 0; i
< originalData
.length
; i
++) {
3128 var data
= originalData
[i
][1];
3130 rollingData
[i
] = [originalData
[i
][0], [y
, y
- data
[0], data
[2] - y
]];
3132 if (y
!= null && !isNaN(y
)) {
3138 if (i
- rollPeriod
>= 0) {
3139 var prev
= originalData
[i
- rollPeriod
];
3140 if (prev
[1][1] != null && !isNaN(prev
[1][1])) {
3147 rollingData
[i
] = [originalData
[i
][0], [ 1.0 * mid
/ count
,
3148 1.0 * (mid
- low
) / count
,
3149 1.0 * (high
- mid
) / count
]];
3152 // Calculate the rolling average for the first rollPeriod - 1 points where
3153 // there is not enough data to roll over the full number of points
3154 var num_init_points
= Math
.min(rollPeriod
- 1, originalData
.length
- 2);
3155 if (!this.attr_("errorBars")){
3156 if (rollPeriod
== 1) {
3157 return originalData
;
3160 for (var i
= 0; i
< originalData
.length
; i
++) {
3163 for (var j
= Math
.max(0, i
- rollPeriod
+ 1); j
< i
+ 1; j
++) {
3164 var y
= originalData
[j
][1];
3165 if (y
== null || isNaN(y
)) continue;
3167 sum
+= originalData
[j
][1];
3170 rollingData
[i
] = [originalData
[i
][0], sum
/ num_ok
];
3172 rollingData
[i
] = [originalData
[i
][0], null];
3177 for (var i
= 0; i
< originalData
.length
; i
++) {
3181 for (var j
= Math
.max(0, i
- rollPeriod
+ 1); j
< i
+ 1; j
++) {
3182 var y
= originalData
[j
][1][0];
3183 if (y
== null || isNaN(y
)) continue;
3185 sum
+= originalData
[j
][1][0];
3186 variance
+= Math
.pow(originalData
[j
][1][1], 2);
3189 var stddev
= Math
.sqrt(variance
) / num_ok
;
3190 rollingData
[i
] = [originalData
[i
][0],
3191 [sum
/ num_ok
, sigma
* stddev
, sigma
* stddev
]];
3193 rollingData
[i
] = [originalData
[i
][0], [null, null, null]];
3204 * Parses a date, returning the number of milliseconds since epoch. This can be
3205 * passed in as an xValueParser in the Dygraph constructor.
3206 * TODO(danvk): enumerate formats that this understands.
3207 * @param {String} A date in YYYYMMDD format.
3208 * @return {Number} Milliseconds since epoch.
3210 Dygraph
.dateParser
= function(dateStr
, self
) {
3213 if (dateStr
.search("-") != -1) { // e.g. '2009-7-12' or '2009-07-12'
3214 dateStrSlashed
= dateStr
.replace("-", "/", "g");
3215 while (dateStrSlashed
.search("-") != -1) {
3216 dateStrSlashed
= dateStrSlashed
.replace("-", "/");
3218 d
= Dygraph
.dateStrToMillis(dateStrSlashed
);
3219 } else if (dateStr
.length
== 8) { // e.g. '20090712'
3220 // TODO(danvk): remove support for this format. It's confusing.
3221 dateStrSlashed
= dateStr
.substr(0,4) + "/" + dateStr
.substr(4,2)
3222 + "/" + dateStr
.substr(6,2);
3223 d
= Dygraph
.dateStrToMillis(dateStrSlashed
);
3225 // Any format that Date.parse will accept, e.g. "2009/07/12" or
3226 // "2009/07/12 12:34:56"
3227 d
= Dygraph
.dateStrToMillis(dateStr
);
3230 if (!d
|| isNaN(d
)) {
3231 self
.error("Couldn't parse " + dateStr
+ " as a date");
3237 * Detects the type of the str (date or numeric) and sets the various
3238 * formatting attributes in this.attrs_ based on this type.
3239 * @param {String} str An x value.
3242 Dygraph
.prototype.detectTypeFromString_
= function(str
) {
3244 if (str
.indexOf('-') > 0 ||
3245 str
.indexOf('/') >= 0 ||
3246 isNaN(parseFloat(str
))) {
3248 } else if (str
.length
== 8 && str
> '19700101' && str
< '20371231') {
3249 // TODO(danvk): remove support for this format.
3254 this.attrs_
.xValueFormatter
= Dygraph
.dateString_
;
3255 this.attrs_
.xValueParser
= Dygraph
.dateParser
;
3256 this.attrs_
.xTicker
= Dygraph
.dateTicker
;
3257 this.attrs_
.xAxisLabelFormatter
= Dygraph
.dateAxisFormatter
;
3259 // TODO(danvk): use Dygraph.numberFormatter here?
3260 /** @private (shut up, jsdoc!) */
3261 this.attrs_
.xValueFormatter
= function(x
) { return x
; };
3262 /** @private (shut up, jsdoc!) */
3263 this.attrs_
.xValueParser
= function(x
) { return parseFloat(x
); };
3264 this.attrs_
.xTicker
= Dygraph
.numericTicks
;
3265 this.attrs_
.xAxisLabelFormatter
= this.attrs_
.xValueFormatter
;
3270 * Parses the value as a floating point number. This is like the parseFloat()
3271 * built-in, but with a few differences:
3272 * - the empty string is parsed as null, rather than NaN.
3273 * - if the string cannot be parsed at all, an error is logged.
3274 * If the string can't be parsed, this method returns null.
3275 * @param {String} x The string to be parsed
3276 * @param {Number} opt_line_no The line number from which the string comes.
3277 * @param {String} opt_line The text of the line from which the string comes.
3281 // Parse the x as a float or return null if it's not a number.
3282 Dygraph
.prototype.parseFloat_
= function(x
, opt_line_no
, opt_line
) {
3283 var val
= parseFloat(x
);
3284 if (!isNaN(val
)) return val
;
3286 // Try to figure out what happeend.
3287 // If the value is the empty string, parse it as null.
3288 if (/^ *$/.test(x
)) return null;
3290 // If it was actually "NaN", return it as NaN.
3291 if (/^ *nan *$/i.test(x
)) return NaN
;
3293 // Looks like a parsing error.
3294 var msg
= "Unable to parse '" + x
+ "' as a number";
3295 if (opt_line
!== null && opt_line_no
!== null) {
3296 msg
+= " on line " + (1+opt_line_no
) + " ('" + opt_line
+ "') of CSV.";
3305 * Parses a string in a special csv format. We expect a csv file where each
3306 * line is a date point, and the first field in each line is the date string.
3307 * We also expect that all remaining fields represent series.
3308 * if the errorBars attribute is set, then interpret the fields as:
3309 * date, series1, stddev1, series2, stddev2, ...
3310 * @param {[Object]} data See above.
3312 * @return [Object] An array with one entry for each row. These entries
3313 * are an array of cells in that row. The first entry is the parsed x-value for
3314 * the row. The second, third, etc. are the y-values. These can take on one of
3315 * three forms, depending on the CSV and constructor parameters:
3317 * 2. [ value, stddev ]
3318 * 3. [ low value, center value, high value ]
3320 Dygraph
.prototype.parseCSV_
= function(data
) {
3322 var lines
= data
.split("\n");
3324 // Use the default delimiter or fall back to a tab if that makes sense.
3325 var delim
= this.attr_('delimiter');
3326 if (lines
[0].indexOf(delim
) == -1 && lines
[0].indexOf('\t') >= 0) {
3331 if (this.labelsFromCSV_
) {
3333 this.attrs_
.labels
= lines
[0].split(delim
);
3338 var defaultParserSet
= false; // attempt to auto-detect x value type
3339 var expectedCols
= this.attr_("labels").length
;
3340 var outOfOrder
= false;
3341 for (var i
= start
; i
< lines
.length
; i
++) {
3342 var line
= lines
[i
];
3344 if (line
.length
== 0) continue; // skip blank lines
3345 if (line
[0] == '#') continue; // skip comment lines
3346 var inFields
= line
.split(delim
);
3347 if (inFields
.length
< 2) continue;
3350 if (!defaultParserSet
) {
3351 this.detectTypeFromString_(inFields
[0]);
3352 xParser
= this.attr_("xValueParser");
3353 defaultParserSet
= true;
3355 fields
[0] = xParser(inFields
[0], this);
3357 // If fractions are expected, parse the numbers as "A/B
"
3358 if (this.fractions_) {
3359 for (var j = 1; j < inFields.length; j++) {
3360 // TODO(danvk): figure out an appropriate way to flag parse errors.
3361 var vals = inFields[j].split("/");
3362 if (vals.length != 2) {
3363 this.error('Expected fractional "num
/den
" values in CSV data ' +
3364 "but found a value
'" + inFields[j] + "' on line
" +
3365 (1 + i) + " ('" + line + "') which is not of
this form
.");
3368 fields[j] = [this.parseFloat_(vals[0], i, line),
3369 this.parseFloat_(vals[1], i, line)];
3372 } else if (this.attr_("errorBars
")) {
3373 // If there are error bars, values are (value, stddev) pairs
3374 if (inFields.length % 2 != 1) {
3375 this.error('Expected alternating (value, stdev.) pairs in CSV data ' +
3376 'but line ' + (1 + i) + ' has an odd number of values (' +
3377 (inFields.length - 1) + "): '" + line + "'");
3379 for (var j = 1; j < inFields.length; j += 2) {
3380 fields[(j + 1) / 2] = [this.parseFloat_(inFields[j], i, line),
3381 this.parseFloat_(inFields[j + 1], i, line)];
3383 } else if (this.attr_("customBars
")) {
3384 // Bars are a low;center;high tuple
3385 for (var j = 1; j < inFields.length; j++) {
3386 var val = inFields[j];
3387 if (/^ *$/.test(val)) {
3388 fields[j] = [null, null, null];
3390 var vals = val.split(";");
3391 if (vals.length == 3) {
3392 fields[j] = [ this.parseFloat_(vals[0], i, line),
3393 this.parseFloat_(vals[1], i, line),
3394 this.parseFloat_(vals[2], i, line) ];
3396 this.warning('When using customBars, values must be either blank ' +
3397 'or "low
;center
;high
" tuples (got "' + val +
3398 '" on line ' + (1+i));
3403 // Values are just numbers
3404 for (var j = 1; j < inFields.length; j++) {
3405 fields[j] = this.parseFloat_(inFields[j], i, line);
3408 if (ret.length > 0 && fields[0] < ret[ret.length - 1][0]) {
3412 if (fields.length != expectedCols) {
3413 this.error("Number of columns
in line
" + i + " (" + fields.length +
3414 ") does not agree
with number of
labels (" + expectedCols +
3418 // If the user specified the 'labels' option and none of the cells of the
3419 // first row parsed correctly, then they probably double-specified the
3420 // labels. We go with the values set in the option, discard this row and
3421 // log a warning to the JS console.
3422 if (i == 0 && this.attr_('labels')) {
3423 var all_null = true;
3424 for (var j = 0; all_null && j < fields.length; j++) {
3425 if (fields[j]) all_null = false;
3428 this.warn("The dygraphs
'labels' option is set
, but the first row of
" +
3429 "CSV
data ('" + line + "') appears to also contain labels
. " +
3430 "Will drop the CSV labels and
use the option labels
.");
3438 this.warn("CSV is out of order
; order it correctly to speed loading
.");
3439 ret.sort(function(a,b) { return a[0] - b[0] });
3447 * The user has provided their data as a pre-packaged JS array. If the x values
3448 * are numeric, this is the same as dygraphs' internal format. If the x values
3449 * are dates, we need to convert them from Date objects to ms since epoch.
3450 * @param {[Object]} data
3451 * @return {[Object]} data with numeric x values.
3453 Dygraph.prototype.parseArray_ = function(data) {
3454 // Peek at the first x value to see if it's numeric.
3455 if (data.length == 0) {
3456 this.error("Can
't plot empty data set");
3459 if (data[0].length == 0) {
3460 this.error("Data set cannot contain an empty row");
3464 if (this.attr_("labels") == null) {
3465 this.warn("Using default labels. Set labels explicitly via 'labels
' " +
3466 "in the options parameter");
3467 this.attrs_.labels = [ "X" ];
3468 for (var i = 1; i < data[0].length; i++) {
3469 this.attrs_.labels.push("Y" + i);
3473 if (Dygraph.isDateLike(data[0][0])) {
3474 // Some intelligent defaults for a date x-axis.
3475 this.attrs_.xValueFormatter = Dygraph.dateString_;
3476 this.attrs_.xAxisLabelFormatter = Dygraph.dateAxisFormatter;
3477 this.attrs_.xTicker = Dygraph.dateTicker;
3479 // Assume they're all dates
.
3480 var parsedData
= Dygraph
.clone(data
);
3481 for (var i
= 0; i
< data
.length
; i
++) {
3482 if (parsedData
[i
].length
== 0) {
3483 this.error("Row " + (1 + i
) + " of data is empty");
3486 if (parsedData
[i
][0] == null
3487 || typeof(parsedData
[i
][0].getTime
) != 'function'
3488 || isNaN(parsedData
[i
][0].getTime())) {
3489 this.error("x value in row " + (1 + i
) + " is not a Date");
3492 parsedData
[i
][0] = parsedData
[i
][0].getTime();
3496 // Some intelligent defaults for a numeric x-axis.
3497 /** @private (shut up, jsdoc!) */
3498 this.attrs_
.xValueFormatter
= function(x
) { return x
; };
3499 this.attrs_
.xTicker
= Dygraph
.numericTicks
;
3505 * Parses a DataTable object from gviz.
3506 * The data is expected to have a first column that is either a date or a
3507 * number. All subsequent columns must be numbers. If there is a clear mismatch
3508 * between this.xValueParser_ and the type of the first column, it will be
3509 * fixed. Fills out rawData_.
3510 * @param {[Object]} data See above.
3513 Dygraph
.prototype.parseDataTable_
= function(data
) {
3514 var cols
= data
.getNumberOfColumns();
3515 var rows
= data
.getNumberOfRows();
3517 var indepType
= data
.getColumnType(0);
3518 if (indepType
== 'date' || indepType
== 'datetime') {
3519 this.attrs_
.xValueFormatter
= Dygraph
.dateString_
;
3520 this.attrs_
.xValueParser
= Dygraph
.dateParser
;
3521 this.attrs_
.xTicker
= Dygraph
.dateTicker
;
3522 this.attrs_
.xAxisLabelFormatter
= Dygraph
.dateAxisFormatter
;
3523 } else if (indepType
== 'number') {
3524 this.attrs_
.xValueFormatter
= function(x
) { return x
; };
3525 this.attrs_
.xValueParser
= function(x
) { return parseFloat(x
); };
3526 this.attrs_
.xTicker
= Dygraph
.numericTicks
;
3527 this.attrs_
.xAxisLabelFormatter
= this.attrs_
.xValueFormatter
;
3529 this.error("only 'date', 'datetime' and 'number' types are supported for " +
3530 "column 1 of DataTable input (Got '" + indepType
+ "')");
3534 // Array of the column indices which contain data (and not annotations).
3536 var annotationCols
= {}; // data index -> [annotation cols]
3537 var hasAnnotations
= false;
3538 for (var i
= 1; i
< cols
; i
++) {
3539 var type
= data
.getColumnType(i
);
3540 if (type
== 'number') {
3542 } else if (type
== 'string' && this.attr_('displayAnnotations')) {
3543 // This is OK -- it's an annotation column.
3544 var dataIdx
= colIdx
[colIdx
.length
- 1];
3545 if (!annotationCols
.hasOwnProperty(dataIdx
)) {
3546 annotationCols
[dataIdx
] = [i
];
3548 annotationCols
[dataIdx
].push(i
);
3550 hasAnnotations
= true;
3552 this.error("Only 'number' is supported as a dependent type with Gviz." +
3553 " 'string' is only supported if displayAnnotations is true");
3557 // Read column labels
3558 // TODO(danvk): add support back for errorBars
3559 var labels
= [data
.getColumnLabel(0)];
3560 for (var i
= 0; i
< colIdx
.length
; i
++) {
3561 labels
.push(data
.getColumnLabel(colIdx
[i
]));
3562 if (this.attr_("errorBars")) i
+= 1;
3564 this.attrs_
.labels
= labels
;
3565 cols
= labels
.length
;
3568 var outOfOrder
= false;
3569 var annotations
= [];
3570 for (var i
= 0; i
< rows
; i
++) {
3572 if (typeof(data
.getValue(i
, 0)) === 'undefined' ||
3573 data
.getValue(i
, 0) === null) {
3574 this.warn("Ignoring row " + i
+
3575 " of DataTable because of undefined or null first column.");
3579 if (indepType
== 'date' || indepType
== 'datetime') {
3580 row
.push(data
.getValue(i
, 0).getTime());
3582 row
.push(data
.getValue(i
, 0));
3584 if (!this.attr_("errorBars")) {
3585 for (var j
= 0; j
< colIdx
.length
; j
++) {
3586 var col
= colIdx
[j
];
3587 row
.push(data
.getValue(i
, col
));
3588 if (hasAnnotations
&&
3589 annotationCols
.hasOwnProperty(col
) &&
3590 data
.getValue(i
, annotationCols
[col
][0]) != null) {
3592 ann
.series
= data
.getColumnLabel(col
);
3594 ann
.shortText
= String
.fromCharCode(65 /* A */ + annotations
.length
)
3596 for (var k
= 0; k
< annotationCols
[col
].length
; k
++) {
3597 if (k
) ann
.text
+= "\n";
3598 ann
.text
+= data
.getValue(i
, annotationCols
[col
][k
]);
3600 annotations
.push(ann
);
3604 // Strip out infinities, which give dygraphs problems later on.
3605 for (var j
= 0; j
< row
.length
; j
++) {
3606 if (!isFinite(row
[j
])) row
[j
] = null;
3609 for (var j
= 0; j
< cols
- 1; j
++) {
3610 row
.push([ data
.getValue(i
, 1 + 2 * j
), data
.getValue(i
, 2 + 2 * j
) ]);
3613 if (ret
.length
> 0 && row
[0] < ret
[ret
.length
- 1][0]) {
3620 this.warn("DataTable is out of order; order it correctly to speed loading.");
3621 ret
.sort(function(a
,b
) { return a
[0] - b
[0] });
3623 this.rawData_
= ret
;
3625 if (annotations
.length
> 0) {
3626 this.setAnnotations(annotations
, true);
3632 * This is identical to JavaScript's built-in Date.parse() method, except that
3633 * it doesn't get replaced with an incompatible method by aggressive JS
3634 * libraries like MooTools or Joomla.
3635 * @param { String } str The date string, e.g. "2011/05/06"
3636 * @return { Integer } millis since epoch
3638 Dygraph
.dateStrToMillis
= function(str
) {
3639 return new Date(str
).getTime();
3642 // These functions are all based on MochiKit.
3646 Dygraph
.update
= function (self
, o
) {
3647 if (typeof(o
) != 'undefined' && o
!== null) {
3649 if (o
.hasOwnProperty(k
)) {
3660 Dygraph
.isArrayLike
= function (o
) {
3661 var typ
= typeof(o
);
3663 (typ
!= 'object' && !(typ
== 'function' &&
3664 typeof(o
.item
) == 'function')) ||
3666 typeof(o
.length
) != 'number' ||
3677 Dygraph
.isDateLike
= function (o
) {
3678 if (typeof(o
) != "object" || o
=== null ||
3679 typeof(o
.getTime
) != 'function') {
3688 Dygraph
.clone
= function(o
) {
3689 // TODO(danvk): figure out how MochiKit's version works
3691 for (var i
= 0; i
< o
.length
; i
++) {
3692 if (Dygraph
.isArrayLike(o
[i
])) {
3693 r
.push(Dygraph
.clone(o
[i
]));
3703 * Get the CSV data. If it's in a function, call that function. If it's in a
3704 * file, do an XMLHttpRequest to get it.
3707 Dygraph
.prototype.start_
= function() {
3708 if (typeof this.file_
== 'function') {
3709 // CSV string. Pretend we got it via XHR.
3710 this.loadedEvent_(this.file_());
3711 } else if (Dygraph
.isArrayLike(this.file_
)) {
3712 this.rawData_
= this.parseArray_(this.file_
);
3714 } else if (typeof this.file_
== 'object' &&
3715 typeof this.file_
.getColumnRange
== 'function') {
3716 // must be a DataTable from gviz.
3717 this.parseDataTable_(this.file_
);
3719 } else if (typeof this.file_
== 'string') {
3720 // Heuristic: a newline means it's CSV data. Otherwise it's an URL.
3721 if (this.file_
.indexOf('\n') >= 0) {
3722 this.loadedEvent_(this.file_
);
3724 var req
= new XMLHttpRequest();
3726 req
.onreadystatechange
= function () {
3727 if (req
.readyState
== 4) {
3728 if (req
.status
== 200) {
3729 caller
.loadedEvent_(req
.responseText
);
3734 req
.open("GET", this.file_
, true);
3738 this.error("Unknown data format: " + (typeof this.file_
));
3743 * Changes various properties of the graph. These can include:
3745 * <li>file: changes the source data for the graph</li>
3746 * <li>errorBars: changes whether the data contains stddev</li>
3749 * @param {Object} attrs The new properties and values
3751 Dygraph
.prototype.updateOptions
= function(attrs
) {
3752 // TODO(danvk): this is a mess. Rethink this function.
3753 if ('rollPeriod' in attrs
) {
3754 this.rollPeriod_
= attrs
.rollPeriod
;
3756 if ('dateWindow' in attrs
) {
3757 this.dateWindow_
= attrs
.dateWindow
;
3758 if (!('isZoomedIgnoreProgrammaticZoom' in attrs
)) {
3759 this.zoomed_x_
= attrs
.dateWindow
!= null;
3762 if ('valueRange' in attrs
&& !('isZoomedIgnoreProgrammaticZoom' in attrs
)) {
3763 this.zoomed_y_
= attrs
.valueRange
!= null;
3766 // TODO(danvk): validate per-series options.
3771 // highlightCircleSize
3773 Dygraph
.update(this.user_attrs_
, attrs
);
3774 Dygraph
.update(this.renderOptions_
, attrs
);
3776 this.labelsFromCSV_
= (this.attr_("labels") == null);
3778 if (attrs
['file']) {
3779 this.file_
= attrs
['file'];
3787 * Resizes the dygraph. If no parameters are specified, resizes to fill the
3788 * containing div (which has presumably changed size since the dygraph was
3789 * instantiated. If the width/height are specified, the div will be resized.
3791 * This is far more efficient than destroying and re-instantiating a
3792 * Dygraph, since it doesn't have to reparse the underlying data.
3794 * @param {Number} [width] Width (in pixels)
3795 * @param {Number} [height] Height (in pixels)
3797 Dygraph
.prototype.resize
= function(width
, height
) {
3798 if (this.resize_lock
) {
3801 this.resize_lock
= true;
3803 if ((width
=== null) != (height
=== null)) {
3804 this.warn("Dygraph.resize() should be called with zero parameters or " +
3805 "two non-NULL parameters. Pretending it was zero.");
3806 width
= height
= null;
3809 // TODO(danvk): there should be a clear() method.
3810 this.maindiv_
.innerHTML
= "";
3811 this.attrs_
.labelsDiv
= null;
3814 this.maindiv_
.style
.width
= width
+ "px";
3815 this.maindiv_
.style
.height
= height
+ "px";
3816 this.width_
= width
;
3817 this.height_
= height
;
3819 this.width_
= this.maindiv_
.offsetWidth
;
3820 this.height_
= this.maindiv_
.offsetHeight
;
3823 this.createInterface_();
3826 this.resize_lock
= false;
3830 * Adjusts the number of points in the rolling average. Updates the graph to
3831 * reflect the new averaging period.
3832 * @param {Number} length Number of points over which to average the data.
3834 Dygraph
.prototype.adjustRoll
= function(length
) {
3835 this.rollPeriod_
= length
;
3840 * Returns a boolean array of visibility statuses.
3842 Dygraph
.prototype.visibility
= function() {
3843 // Do lazy-initialization, so that this happens after we know the number of
3845 if (!this.attr_("visibility")) {
3846 this.attrs_
["visibility"] = [];
3848 while (this.attr_("visibility").length
< this.rawData_
[0].length
- 1) {
3849 this.attr_("visibility").push(true);
3851 return this.attr_("visibility");
3855 * Changes the visiblity of a series.
3857 Dygraph
.prototype.setVisibility
= function(num
, value
) {
3858 var x
= this.visibility();
3859 if (num
< 0 || num
>= x
.length
) {
3860 this.warn("invalid series number in setVisibility: " + num
);
3868 * Update the list of annotations and redraw the chart.
3870 Dygraph
.prototype.setAnnotations
= function(ann
, suppressDraw
) {
3871 // Only add the annotation CSS rule once we know it will be used.
3872 Dygraph
.addAnnotationRule();
3873 this.annotations_
= ann
;
3874 this.layout_
.setAnnotations(this.annotations_
);
3875 if (!suppressDraw
) {
3881 * Return the list of annotations.
3883 Dygraph
.prototype.annotations
= function() {
3884 return this.annotations_
;
3888 * Get the index of a series (column) given its name. The first column is the
3889 * x-axis, so the data series start with index 1.
3891 Dygraph
.prototype.indexFromSetName
= function(name
) {
3892 var labels
= this.attr_("labels");
3893 for (var i
= 0; i
< labels
.length
; i
++) {
3894 if (labels
[i
] == name
) return i
;
3901 * Adds a default style for the annotation CSS classes to the document. This is
3902 * only executed when annotations are actually used. It is designed to only be
3903 * called once -- all calls after the first will return immediately.
3905 Dygraph
.addAnnotationRule
= function() {
3906 if (Dygraph
.addedAnnotationCSS
) return;
3908 var rule
= "border: 1px solid black; " +
3909 "background-color: white; " +
3910 "text-align: center;";
3912 var styleSheetElement
= document
.createElement("style");
3913 styleSheetElement
.type
= "text/css";
3914 document
.getElementsByTagName("head")[0].appendChild(styleSheetElement
);
3916 // Find the first style sheet that we can access.
3917 // We may not add a rule to a style sheet from another domain for security
3918 // reasons. This sometimes comes up when using gviz, since the Google gviz JS
3919 // adds its own style sheets from google.com.
3920 for (var i
= 0; i
< document
.styleSheets
.length
; i
++) {
3921 if (document
.styleSheets
[i
].disabled
) continue;
3922 var mysheet
= document
.styleSheets
[i
];
3924 if (mysheet
.insertRule
) { // Firefox
3925 var idx
= mysheet
.cssRules
? mysheet
.cssRules
.length
: 0;
3926 mysheet
.insertRule(".dygraphDefaultAnnotation { " + rule
+ " }", idx
);
3927 } else if (mysheet
.addRule
) { // IE
3928 mysheet
.addRule(".dygraphDefaultAnnotation", rule
);
3930 Dygraph
.addedAnnotationCSS
= true;
3933 // Was likely a security exception.
3937 this.warn("Unable to add default annotation CSS rule; display may be off.");
3942 * Create a new canvas element. This is more complex than a simple
3943 * document.createElement("canvas") because of IE and excanvas.
3945 Dygraph
.createCanvas
= function() {
3946 var canvas
= document
.createElement("canvas");
3948 isIE
= (/MSIE/.test(navigator
.userAgent
) && !window
.opera
);
3949 if (isIE
&& (typeof(G_vmlCanvasManager
) != 'undefined')) {
3950 canvas
= G_vmlCanvasManager
.initElement(canvas
);
3958 * A wrapper around Dygraph that implements the gviz API.
3959 * @param {Object} container The DOM object the visualization should live in.
3961 Dygraph
.GVizChart
= function(container
) {
3962 this.container
= container
;
3965 Dygraph
.GVizChart
.prototype.draw
= function(data
, options
) {
3966 // Clear out any existing dygraph.
3967 // TODO(danvk): would it make more sense to simply redraw using the current
3968 // date_graph object?
3969 this.container
.innerHTML
= '';
3970 if (typeof(this.date_graph
) != 'undefined') {
3971 this.date_graph
.destroy();
3974 this.date_graph
= new Dygraph(this.container
, data
, options
);
3978 * Google charts compatible setSelection
3979 * Only row selection is supported, all points in the row will be highlighted
3980 * @param {Array} array of the selected cells
3983 Dygraph
.GVizChart
.prototype.setSelection
= function(selection_array
) {
3985 if (selection_array
.length
) {
3986 row
= selection_array
[0].row
;
3988 this.date_graph
.setSelection(row
);
3992 * Google charts compatible getSelection implementation
3993 * @return {Array} array of the selected cells
3996 Dygraph
.GVizChart
.prototype.getSelection
= function() {
3999 var row
= this.date_graph
.getSelection();
4001 if (row
< 0) return selection
;
4004 for (var i
in this.date_graph
.layout_
.datasets
) {
4005 selection
.push({row
: row
, column
: col
});
4012 // Older pages may still use this name.
4013 DateGraph
= Dygraph
;
4015 // <REMOVE_FOR_COMBINED>
4016 Dygraph
.OPTIONS_REFERENCE
= // <JSON>
4019 "default": "parseFloat() or Date.parse()*",
4020 "labels": ["CSV parsing"],
4021 "type": "function(str) -> number",
4022 "description": "A function which parses x-values (i.e. the dependent series). Must return a number, even when the values are dates. In this case, millis since epoch are used. This is used primarily for parsing CSV data. *=Dygraphs is slightly more accepting in the dates which it will parse. See code for details."
4026 "labels": ["Data Line display"],
4028 "description": "If set, stack series on top of one another rather than drawing them independently."
4032 "labels": ["Data Line display"],
4034 "description": "The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is \"isolated\", i.e. there is a missing point on either side of it. This also controls the size of those dots."
4036 "labelsDivStyles": {
4038 "labels": ["Legend"],
4040 "description": "Additional styles to apply to the currently-highlighted points div. For example, { 'font-weight': 'bold' } will make the labels bold."
4044 "labels": ["Data Line display"],
4046 "description": "Draw a small dot at each point, in addition to a line going through the point. This makes the individual data points easier to see, but can increase visual clutter in the chart."
4050 "labels": ["Overall display"],
4052 "description": "Height, in pixels, of the chart. If the container div has been explicitly sized, this will be ignored."
4056 "labels": ["Callbacks"],
4057 "type": "function(minDate, maxDate, yRanges)",
4058 "description": "A function to call when the zoom window is changed (either by zooming in or out). minDate and maxDate are milliseconds since epoch. yRanges is an array of [bottom, top] pairs, one for each y-axis."
4060 "pointClickCallback": {
4062 "labels": ["Callbacks", "Interactive Elements"],
4067 "default": "(see description)",
4068 "labels": ["Data Series Colors"],
4069 "type": "array<string>",
4070 "example": "['red', '#00FF00']",
4071 "description": "List of colors for the data series. These can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"yellow\", etc. If not specified, equally-spaced points around a color wheel are used."
4073 "connectSeparatedPoints": {
4075 "labels": ["Data Line display"],
4077 "description": "Usually, when Dygraphs encounters a missing value in a data series, it interprets this as a gap and draws it as such. If, instead, the missing values represents an x-value for which only a different series has data, then you'll want to connect the dots by setting this to true. To explicitly include a gap with this option set, use a value of NaN."
4079 "highlightCallback": {
4081 "labels": ["Callbacks"],
4082 "type": "function(event, x, points,row)",
4083 "description": "When set, this callback gets called every time a new point is highlighted. The parameters are the JavaScript mousemove event, the x-coordinate of the highlighted points and an array of highlighted points: <code>[ {name: 'series', yval: y-value}, … ]</code>"
4087 "labels": ["Axis display"],
4089 "description": "Usually, dygraphs will use the range of the data plus some padding to set the range of the y-axis. If this option is set, the y-axis will always include zero, typically as the lowest value. This can be used to avoid exaggerating the variance in the data"
4093 "labels": ["Error Bars", "Rolling Averages"],
4094 "type": "integer >= 1",
4095 "description": "Number of days over which to average data. Discussed extensively above."
4097 "unhighlightCallback": {
4099 "labels": ["Callbacks"],
4100 "type": "function(event)",
4101 "description": "When set, this callback gets called every time the user stops highlighting any point by mousing out of the graph. The parameter is the mouseout event."
4105 "labels": ["Axis display"],
4107 "description": "The size of the line to display next to each tick mark on x- or y-axes."
4109 "labelsSeparateLines": {
4111 "labels": ["Legend"],
4113 "description": "Put <code><br/></code> between lines in the label string. Often used in conjunction with <strong>labelsDiv</strong>."
4115 "xValueFormatter": {
4116 "default": "(Round to 2 decimal places)",
4117 "labels": ["Axis display"],
4118 "type": "function(x)",
4119 "description": "Function to provide a custom display format for the X value for mouseover."
4121 "pixelsPerYLabel": {
4123 "labels": ["Axis display", "Grid"],
4125 "description": "Number of pixels to require between each x- and y-label. Larger values will yield a sparser axis with fewer ticks."
4127 "annotationMouseOverHandler": {
4129 "labels": ["Annotations"],
4130 "type": "function(annotation, point, dygraph, event)",
4131 "description": "If provided, this function is called whenever the user mouses over an annotation."
4133 "annotationMouseOutHandler": {
4135 "labels": ["Annotations"],
4136 "type": "function(annotation, point, dygraph, event)",
4137 "description": "If provided, this function is called whenever the user mouses out of an annotation."
4139 "annotationClickHandler": {
4141 "labels": ["Annotations"],
4142 "type": "function(annotation, point, dygraph, event)",
4143 "description": "If provided, this function is called whenever the user clicks on an annotation."
4145 "annotationDblClickHandler": {
4147 "labels": ["Annotations"],
4148 "type": "function(annotation, point, dygraph, event)",
4149 "description": "If provided, this function is called whenever the user double-clicks on an annotation."
4153 "labels": ["Callbacks"],
4154 "type": "function(dygraph, is_initial)",
4155 "description": "When set, this callback gets called every time the dygraph is drawn. This includes the initial draw, after zooming and repeatedly while panning. The first parameter is the dygraph being drawn. The second is a boolean value indicating whether this is the initial draw."
4159 "labels": ["Value display/formatting"],
4161 "description": "Show k/M/G for kilo/Mega/Giga on y-axis. This is different than <code>labelsKMB</code> in that it uses base 2, not 10."
4165 "labels": ["CSV parsing"],
4167 "description": "The delimiter to look for when separating fields of a CSV file. Setting this to a tab is not usually necessary, since tab-delimited data is auto-detected."
4169 "axisLabelFontSize": {
4171 "labels": ["Axis display"],
4173 "description": "Size of the font (in pixels) to use in the axis labels, both x- and y-axis."
4175 "underlayCallback": {
4177 "labels": ["Callbacks"],
4178 "type": "function(canvas, area, dygraph)",
4179 "description": "When set, this callback gets called before the chart is drawn. It details on how to use this."
4183 "labels": ["Overall display"],
4185 "description": "Width, in pixels, of the chart. If the container div has been explicitly sized, this will be ignored."
4187 "interactionModel": {
4189 "labels": ["Interactive Elements"],
4191 "description": "TODO(konigsberg): document this"
4194 "default": "Dygraph.dateTicker or Dygraph.numericTicks",
4195 "labels": ["Axis display"],
4196 "type": "function(min, max, dygraph) -> [{v: ..., label: ...}, ...]",
4197 "description": "This lets you specify an arbitrary function to generate tick marks on an axis. The tick marks are an array of (value, label) pairs. The built-in functions go to great lengths to choose good tick marks so, if you set this option, you'll most likely want to call one of them and modify the result."
4199 "xAxisLabelWidth": {
4201 "labels": ["Axis display"],
4203 "description": "Width, in pixels, of the x-axis labels."
4205 "showLabelsOnHighlight": {
4207 "labels": ["Interactive Elements", "Legend"],
4209 "description": "Whether to show the legend upon mouseover."
4212 "default": "(none)",
4213 "labels": ["Axis display"],
4214 "type": "string or object",
4215 "description": "Set to either an object ({}) filled with options for this axis or to the name of an existing data series with its own axis to re-use that axis. See tests for usage."
4217 "pixelsPerXLabel": {
4219 "labels": ["Axis display", "Grid"],
4221 "description": "Number of pixels to require between each x- and y-label. Larger values will yield a sparser axis with fewer ticks."
4225 "labels": ["Legend"],
4226 "type": "DOM element or string",
4227 "example": "<code style='font-size: small'>document.getElementById('foo')</code>or<code>'foo'",
4228 "description": "Show data labels in an external div, rather than on the graph. This value can either be a div element or a div id."
4232 "labels": ["CSV parsing", "Error Bars"],
4234 "description": "When set, attempt to parse each cell in the CSV file as \"a/b\", where a and b are integers. The ratio will be plotted. This allows computation of Wilson confidence intervals (see below)."
4238 "labels": ["Axis display"],
4240 "description": "When set for a y-axis, the graph shows that axis in log scale. Any values less than or equal to zero are not displayed.\n\nNot compatible with showZero, and ignores connectSeparatedPoints. Also, showing log scale with valueRanges that are less than zero will result in an unviewable graph."
4244 "labels": ["Data Line display"],
4246 "example": "0.5, 2.0",
4247 "description": "The width of the lines connecting data points. This can be used to increase the contrast or some graphs."
4251 "labels": ["Error Bars"],
4253 "description": "Use in conjunction with the \"fractions\" option. Instead of plotting +/- N standard deviations, dygraphs will compute a Wilson confidence interval and plot that. This has more reasonable behavior for ratios close to 0 or 1."
4257 "labels": ["Data Line display"],
4259 "description": "Should the area underneath the graph be filled? This option is not compatible with error bars."
4261 "highlightCircleSize": {
4263 "labels": ["Interactive Elements"],
4265 "description": "The size in pixels of the dot drawn over highlighted points."
4268 "default": "rgb(128,128,128)",
4270 "type": "red, blue",
4271 "description": "The color of the gridlines."
4274 "default": "[true, true, ...]",
4275 "labels": ["Data Line display"],
4276 "type": "Array of booleans",
4277 "description": "Which series should initially be visible? Once the Dygraph has been constructed, you can access and modify the visibility of each series using the <code>visibility</code> and <code>setVisibility</code> methods."
4280 "default": "Full range of the input is shown",
4281 "labels": ["Axis display"],
4282 "type": "Array of two numbers",
4283 "example": "[10, 110]",
4284 "description": "Explicitly set the vertical range of the graph to [low, high]."
4288 "labels": ["Legend"],
4290 "description": "Width (in pixels) of the div which shows information on the currently-highlighted points."
4292 "colorSaturation": {
4294 "labels": ["Data Series Colors"],
4295 "type": "0.0 - 1.0",
4296 "description": "If <strong>colors</strong> is not specified, saturation of the automatically-generated data series colors."
4298 "yAxisLabelWidth": {
4300 "labels": ["Axis display"],
4302 "description": "Width, in pixels, of the y-axis labels."
4304 "hideOverlayOnMouseOut": {
4306 "labels": ["Interactive Elements", "Legend"],
4308 "description": "Whether to hide the legend when the mouse leaves the chart area."
4310 "yValueFormatter": {
4311 "default": "(Round to 2 decimal places)",
4312 "labels": ["Axis display"],
4313 "type": "function(x)",
4314 "description": "Function to provide a custom display format for the Y value for mouseover."
4317 "default": "onmouseover",
4318 "labels": ["Legend"],
4320 "description": "When to display the legend. By default, it only appears when a user mouses over the chart. Set it to \"always\" to always display a legend of some sort."
4322 "labelsShowZeroValues": {
4324 "labels": ["Legend"],
4326 "description": "Show zero value labels in the labelsDiv."
4330 "labels": ["Data Line display"],
4332 "description": "When set, display the graph as a step plot instead of a line plot."
4336 "labels": ["Value display/formatting"],
4338 "description": "Show K/M/B for thousands/millions/billions on y-axis."
4342 "labels": ["Overall display"],
4344 "description": "Number of pixels to leave blank at the right edge of the Dygraph. This makes it easier to highlight the right-most data point."
4348 "labels": ["Axis display"],
4350 "description": "When set, the heuristic that fixes the Y axis at zero for a data set with the minimum Y value of zero is disabled. \nThis is particularly useful for data sets that contain many zero values, especially for step plots which may otherwise have lines not visible running along the bottom axis."
4352 "xAxisLabelFormatter": {
4353 "default": "Dygraph.dateAxisFormatter",
4354 "labels": ["Axis display", "Value display/formatting"],
4355 "type": "function(date, granularity)",
4356 "description": "Function to call to format values along the x axis."
4359 "snippet": "function(e, date){<br> alert(date);<br>}",
4361 "labels": ["Callbacks"],
4362 "type": "function(e, date)",
4363 "description": "A function to call when a data point is clicked. The function should take two arguments, the event object for the click and the date that was clicked."
4365 "yAxisLabelFormatter": {
4366 "default": "yValueFormatter",
4367 "labels": ["Axis display", "Value display/formatting"],
4368 "type": "function(x)",
4369 "description": "Function used to format values along the Y axis. By default it uses the same as the <code>yValueFormatter</code> unless specified."
4372 "default": "[\"X\", \"Y1\", \"Y2\", ...]*",
4373 "labels": ["Legend"],
4374 "type": "array<string>",
4375 "description": "A name for each data series, including the independent (X) series. For CSV files and DataTable objections, this is determined by context. For raw data, this must be specified. If it is not, default values are supplied and a warning is logged."
4378 "default": "Full range of the input is shown",
4379 "labels": ["Axis display"],
4380 "type": "Array of two Dates or numbers",
4381 "example": "[<br> Date.parse('2006-01-01'),<br> (new Date()).valueOf()<br>]",
4382 "description": "Initially zoom in on a section of the graph. Is of the form [earliest, latest], where earliest/latest are milliseconds since epoch. If the data for the x-axis is numeric, the values in dateWindow must also be numbers."
4386 "labels": ["Interactive Elements", "Rolling Averages"],
4388 "description": "If the rolling average period text box should be shown."
4392 "labels": ["Error Bars"],
4394 "description": "When errorBars is set, shade this many standard deviations above/below each point."
4398 "labels": ["CSV parsing", "Error Bars"],
4400 "description": "When set, parse each CSV cell as \"low;middle;high\". Error bars will be drawn for each point between low and high, with the series itself going through middle."
4404 "labels": ["Data Series Colors"],
4405 "type": "float (0.0 - 1.0)",
4406 "description": "If colors is not specified, value of the data series colors, as in hue/saturation/value. (0.0-1.0, default 0.5)"
4410 "labels": ["CSV parsing", "Error Bars"],
4412 "description": "Does the data contain standard deviations? Setting this to true alters the input format (see above)."
4414 "displayAnnotations": {
4416 "labels": ["Annotations"],
4418 "description": "Only applies when Dygraphs is used as a GViz chart. Causes string columns following a data series to be interpreted as annotations on points in that series. This is the same format used by Google's AnnotatedTimeLine chart."
4420 "panEdgeFraction": {
4422 "labels": ["Axis Display", "Interactive Elements"],
4425 "description": "A value representing the farthest a graph may be panned, in percent of the display. For example, a value of 0.1 means that the graph can only be panned 10% pased the edges of the displayed values. null means no bounds."
4428 "labels": ["Chart labels"],
4431 "description": "Text to display above the chart. You can supply any HTML for this value, not just text. If you wish to style it using CSS, use the 'dygraph-label' or 'dygraph-title' classes."
4435 "labels": ["Chart labels"],
4437 "description": "Height of the chart title, in pixels. This also controls the default font size of the title. If you style the title on your own, this controls how much space is set aside above the chart for the title's div."
4440 "labels": ["Chart labels"],
4443 "description": "Text to display below the chart's x-axis. You can supply any HTML for this value, not just text. If you wish to style it using CSS, use the 'dygraph-label' or 'dygraph-xlabel' classes."
4446 "labels": ["Chart labels"],
4449 "description": "Height of the x-axis label, in pixels. This also controls the default font size of the x-axis label. If you style the label on your own, this controls how much space is set aside below the chart for the x-axis label's div."
4452 "labels": ["Chart labels"],
4455 "description": "Text to display to the left of the chart's y-axis. You can supply any HTML for this value, not just text. If you wish to style it using CSS, use the 'dygraph-label' or 'dygraph-ylabel' classes. The text will be rotated 90 degrees by default, so CSS rules may behave in unintuitive ways. No additional space is set aside for a y-axis label. If you need more space, increase the width of the y-axis tick labels using the yAxisLabelWidth option. If you need a wider div for the y-axis label, either style it that way with CSS (but remember that it's rotated, so width is controlled by the 'height' property) or set the yLabelWidth option."
4458 "labels": ["Chart labels"],
4461 "description": "Width of the div which contains the y-axis label. Since the y-axis label appears rotated 90 degrees, this actually affects the height of its div."
4463 "isZoomedIgnoreProgrammaticZoom" : {
4465 "labels": ["Zooming"],
4467 "description" : "When this option is passed to updateOptions() along with either the <code>dateWindow</code> or <code>valueRange</code> options, the zoom flags are not changed to reflect a zoomed state. This is primarily useful for when the display area of a chart is changed programmatically and also where manual zooming is allowed and use is made of the <code>isZoomed</code> method to determine this."
4471 "labels": ["Value display/formatting"],
4473 "description": "By default, dygraphs displays numbers with a fixed number of digits after the decimal point. If you'd prefer to have a fixed number of significant figures, set this option to that number of sig figs. A value of 2, for instance, would cause 1 to be display as 1.0 and 1234 to be displayed as 1.23e+3."
4475 "digitsAfterDecimal" : {
4477 "labels": ["Value display/formatting"],
4479 "description": "Unless it's run in scientific mode (see the <code>sigFigs</code> option), dygraphs displays numbers with <code>digitsAfterDecimal</code> digits after the decimal point. Trailing zeros are not displayed, so with a value of 2 you'll get '0', '0.1', '0.12', '123.45' but not '123.456' (it will be rounded to '123.46'). Numbers with absolute value less than 0.1^digitsAfterDecimal (i.e. those which would show up as '0.00') will be displayed in scientific notation."
4481 "maxNumberWidth" : {
4483 "labels": ["Value display/formatting"],
4485 "description": "When displaying numbers in normal (not scientific) mode, large numbers will be displayed with many trailing zeros (e.g. 100000000 instead of 1e9). This can lead to unwieldy y-axis labels. If there are more than <code>maxNumberWidth</code> digits to the left of the decimal in a number, dygraphs will switch to scientific notation, even when not operating in scientific mode. If you'd like to see all those digits, set this to something large, like 20 or 30."
4489 // NOTE: in addition to parsing as JS, this snippet is expected to be valid
4490 // JSON. This assumption cannot be checked in JS, but it will be checked when
4491 // documentation is generated by the generate-documentation.py script. For the
4492 // most part, this just means that you should always use double quotes.
4494 // Do a quick sanity check on the options reference.
4496 var warn
= function(msg
) { if (console
) console
.warn(msg
); };
4497 var flds
= ['type', 'default', 'description'];
4504 'Data Line display',
4505 'Data Series Colors',
4508 'Interactive Elements',
4512 'Value display/formatting',
4516 for (var i
= 0; i
< valid_cats
.length
; i
++) cats
[valid_cats
[i
]] = true;
4518 for (var k
in Dygraph
.OPTIONS_REFERENCE
) {
4519 if (!Dygraph
.OPTIONS_REFERENCE
.hasOwnProperty(k
)) continue;
4520 var op
= Dygraph
.OPTIONS_REFERENCE
[k
];
4521 for (var i
= 0; i
< flds
.length
; i
++) {
4522 if (!op
.hasOwnProperty(flds
[i
])) {
4523 warn('Option ' + k
+ ' missing "' + flds
[i
] + '" property');
4524 } else if (typeof(op
[flds
[i
]]) != 'string') {
4525 warn(k
+ '.' + flds
[i
] + ' must be of type string');
4528 var labels
= op
['labels'];
4529 if (typeof(labels
) !== 'object') {
4530 warn('Option "' + k
+ '" is missing a "labels": [...] option');
4531 for (var i
= 0; i
< labels
.length
; i
++) {
4532 if (!cats
.hasOwnProperty(labels
[i
])) {
4533 warn('Option "' + k
+ '" has label "' + labels
[i
] +
4534 '", which is invalid.');
4540 // </REMOVE_FOR_COMBINED
>