From cf180220e31315f7a4f4595b80822a661326776c Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Mon, 9 May 2011 07:20:03 -0700 Subject: [PATCH] clear generated jsdoc; add script --- generate-jsdoc.sh | 9 + jsdoc-toolkit/out/jsdoc/files.html | 255 -- jsdoc-toolkit/out/jsdoc/index.html | 218 - jsdoc-toolkit/out/jsdoc/symbols/Dygraph.html | 2586 ----------- jsdoc-toolkit/out/jsdoc/symbols/_global_.html | 251 -- .../out/jsdoc/symbols/src/dygraph.js.html | 4530 -------------------- 6 files changed, 9 insertions(+), 7840 deletions(-) create mode 100644 generate-jsdoc.sh delete mode 100644 jsdoc-toolkit/out/jsdoc/files.html delete mode 100644 jsdoc-toolkit/out/jsdoc/index.html delete mode 100644 jsdoc-toolkit/out/jsdoc/symbols/Dygraph.html delete mode 100644 jsdoc-toolkit/out/jsdoc/symbols/_global_.html delete mode 100644 jsdoc-toolkit/out/jsdoc/symbols/src/dygraph.js.html diff --git a/generate-jsdoc.sh b/generate-jsdoc.sh new file mode 100644 index 0000000..3275545 --- /dev/null +++ b/generate-jsdoc.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# +# Generates JSDoc in the /jsdoc dir. Clears any existing jsdoc there. + +rm -rf jsdoc +java -jar jsdoc-toolkit/jsrun.jar \ + jsdoc-toolkit/app/run.js \ + -a -d=jsdoc -t=jsdoc-toolkit/templates/jsdoc \ + dygraph.js diff --git a/jsdoc-toolkit/out/jsdoc/files.html b/jsdoc-toolkit/out/jsdoc/files.html deleted file mode 100644 index 03e954f..0000000 --- a/jsdoc-toolkit/out/jsdoc/files.html +++ /dev/null @@ -1,255 +0,0 @@ - - - - - - JsDoc Reference - File Index - - - - - - - - -
-
Class Index -| File Index
-
-

Classes

- -
-
- -
-

File Index

- - -
-

dygraph.js

- -Creates an interactive, zoomable graph based on a CSV file or -string. Dygraph can handle multiple series with or without error bars. The -date/value ranges will be automatically set. Dygraph uses the -<canvas> tag, so it only works in FF1.5+. -
- -
Author:
-
danvdk@gmail.com (Dan Vanderkam) - - Usage: -
- - - The CSV file is of the form - - Date,SeriesA,SeriesB,SeriesC - YYYYMMDD,A1,B1,C1 - YYYYMMDD,A2,B2,C2 - - If the 'errorBars' option is set in the constructor, the input should be of - the form - Date,SeriesA,SeriesB,... - YYYYMMDD,A1,sigmaA1,B1,sigmaB1,... - YYYYMMDD,A2,sigmaA2,B2,sigmaB2,... - - If the 'fractions' option is set, the input should be of the form: - - Date,SeriesA,SeriesB,... - YYYYMMDD,A1/B1,A2/B2,... - YYYYMMDD,A1/B1,A2/B2,... - - And error bars will be calculated automatically using a binomial distribution. - - For further documentation and examples, see http://dygraphs.com/
- - - - -
-
-
- - -
-
- - Documentation generated by JsDoc Toolkit 2.4.0 on Mon May 09 2011 07:12:49 GMT-0700 (PDT) -
- - \ No newline at end of file diff --git a/jsdoc-toolkit/out/jsdoc/index.html b/jsdoc-toolkit/out/jsdoc/index.html deleted file mode 100644 index c32dce5..0000000 --- a/jsdoc-toolkit/out/jsdoc/index.html +++ /dev/null @@ -1,218 +0,0 @@ - - - - - - JsDoc Reference - Index - - - - - - - - -
-
Class Index -| File Index
-
-

Classes

- -
-
- -
-

Class Index

- - -
-

_global_

- -
-
- -
-

Dygraph

- -
-
- - -
-
- - Documentation generated by JsDoc Toolkit 2.4.0 on Mon May 09 2011 07:12:49 GMT-0700 (PDT) -
- - \ No newline at end of file diff --git a/jsdoc-toolkit/out/jsdoc/symbols/Dygraph.html b/jsdoc-toolkit/out/jsdoc/symbols/Dygraph.html deleted file mode 100644 index bf40cde..0000000 --- a/jsdoc-toolkit/out/jsdoc/symbols/Dygraph.html +++ /dev/null @@ -1,2586 +0,0 @@ - - - - - - - JsDoc Reference - Dygraph - - - - - - - - - - - -
- -
Class Index -| File Index
-
-

Classes

- -
- -
- -
- -

- - Class Dygraph -

- - -

- - - - - - -
Defined in: dygraph.js. - -

- - - - - - - - - - - - - - - - - -
Class Summary
Constructor AttributesConstructor Name and Description
  -
- Dygraph(div, file, attrs) -
-
Creates an interactive, zoomable chart.
-
- - - - - - - - - - - - - - - - - - - - - - -
Field Summary
Field AttributesField Name and Description
<static>   - -
Default interation model for dygraphs.
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Method Summary
Method AttributesMethod Name and Description
  -
adjustRoll(length) -
-
Adjusts the number of points in the rolling average.
-
  - -
Return the list of annotations.
-
  - -
Clears the current selection (i.e.
-
<static>   -
Dygraph.dateTicker(startDate, endDate, self) -
-
Add ticks to the x-axis based on a date range.
-
  -
destroy() -
-
Detach DOM elements in the dygraph and null out all data references.
-
<static>   -
Dygraph.endPan(event, g, context) -
-
Called in response to an interaction model operation that -responds to an event that ends panning.
-
<static>   -
Dygraph.endZoom(event, g, context) -
-
Called in response to an interaction model operation that -responds to an event that performs a zoom based on previously defined -bounds.
-
<static>   -
Dygraph.floatFormat(x, opt_precision) -
-
Number formatting function which mimicks the behavior of %g in printf, i.e.
-
  - -
Return the list of colors.
-
  - -
Returns the number of the currently selected row.
-
  -
getValue(row, col) -
-
Returns the value in the given row and column.
-
<static>   -
Dygraph.GVizChart(container) -
-
A wrapper around Dygraph that implements the gviz API.
-
  - -
Get the index of a series (column) given its name.
-
  -
isZoomed(axis) -
-
Returns the zoomed status of the chart for one or both axes.
-
<static>   -
Dygraph.movePan(event, g, context) -
-
Called in response to an interaction model operation that -responds to an event that pans the view.
-
<static>   -
Dygraph.moveZoom(event, g, context) -
-
Called in response to an interaction model operation that -responds to an event that defines zoom boundaries.
-
  -
numAxes() -
-
Returns the number of y-axes on the chart.
-
  - -
Returns the number of columns (including the independent variable).
-
<static>   -
Dygraph.numericTicks(minV, maxV, self, attribute, vals) -
-
Add ticks when the x axis has numbers on it (instead of dates)
-
  -
numRows() -
-
Returns the number of rows (excluding any header/label row).
-
  -
resize(width, height) -
-
Resizes the dygraph.
-
  - -
Returns the current rolling period, as set by the user or an option.
-
  -
setAnnotations(ann, suppressDraw) -
-
Update the list of annotations and redraw the chart.
-
  -
setSelection(row) -
-
Manually set the selected points and display information about them in the -legend.
-
  -
setVisibility(num, value) -
-
Changes the visiblity of a series.
-
<static>   -
Dygraph.startPan(event, g, context) -
-
Called in response to an interaction model operation that -should start the default panning behavior.
-
<static>   -
Dygraph.startZoom(event, g, context) -
-
Called in response to an interaction model operation that -responds to an event that starts zooming.
-
  -
toDataCoords(x, y, axis) -
-
Convert from canvas/div coords to data coordinates.
-
  - -
Convert from canvas/div x coordinate to data coordinate.
-
  -
toDataYCoord(y, axis) -
-
Convert from canvas/div y coord to value.
-
  -
toDomCoords(x, y, axis) -
-
Convert from data coordinates to canvas/div X/Y coordinates.
-
  - -
Convert from data x coordinates to canvas/div X coordinate.
-
  -
toDomYCoord(y, axis) -
-
Convert from data x coordinates to canvas/div Y coordinate and optional -axis.
-
  - -
Converts an x value to a percentage from the left to the right of -the drawing area.
-
  -
toPercentYCoord(y, axis) -
-
Converts a y for an axis to a percentage from the top to the -bottom of the drawing area.
-
  - -
Returns information about the Dygraph object, including its containing ID.
-
<static>   -
Dygraph.toString() -
-
Returns information about the Dygraph class.
-
  -
updateOptions(attrs) -
-
Changes various properties of the graph.
-
  - -
Returns a boolean array of visibility statuses.
-
  - -
Returns the lower- and upper-bound x-axis values of the -data set.
-
  - -
Returns the currently-visible x-range.
-
  -
yAxisRange(idx) -
-
Returns the currently-visible y-range for an axis.
-
  - -
Returns the currently-visible y-ranges for each axis.
-
- - - - - - - - - -
-
- Class Detail -
- -
- Dygraph(div, file, attrs) -
- -
- Creates an interactive, zoomable chart. - -
- - - - - -
-
Parameters:
- -
- {div | String} div - -
-
A div or the id of a div into which to construct -the chart.
- -
- {String | Function} file - -
-
A file containing CSV data or a function -that returns this data. The most basic expected format for each line is -"YYYY/MM/DD,val1,val2,...". For more information, see -http://dygraphs.com/data.html.
- -
- {Object} attrs - -
-
Various other attributes, e.g. errorBars determines -whether the input data contains error ranges. For a complete list of -options, see http://dygraphs.com/options.html.
- -
- - - - - - - - -
- - - - -
- Field Detail -
- - -
<static> - - - Dygraph.defaultInteractionModel - -
-
- Default interation model for dygraphs. You can refer to specific elements of -this when constructing your own interaction model, e.g.: -g.updateOptions( { - interactionModel: { - mousedown: Dygraph.defaultInteractionModel.mousedown - } -} ); - - -
- - - - - - - - - - - - - - -
- Method Detail -
- - -
- - - adjustRoll(length) - -
-
- Adjusts the number of points in the rolling average. Updates the graph to -reflect the new averaging period. - - -
- - - - -
-
Parameters:
- -
- {Number} length - -
-
Number of points over which to average the data.
- -
- - - - - - - - -
- - -
- - - annotations() - -
-
- Return the list of annotations. - - -
- - - - - - - - - - - -
- - -
- - - clearSelection() - -
-
- Clears the current selection (i.e. points that were highlighted by moving -the mouse over the chart). - - -
- - - - - - - - - - - -
- - -
<static> - - {[Object]} - Dygraph.dateTicker(startDate, endDate, self) - -
-
- Add ticks to the x-axis based on a date range. - - -
- - - - -
-
Parameters:
- -
- {Number} startDate - -
-
Start of the date window (millis since epoch)
- -
- {Number} endDate - -
-
End of the date window (millis since epoch)
- -
- {Dygraph} self - -
-
The dygraph object
- -
- - - - - -
-
Returns:
- -
{[Object]} Array of {label, value} tuples.
- -
- - - - -
- - -
- - - destroy() - -
-
- Detach DOM elements in the dygraph and null out all data references. -Calling this when you're done with a dygraph can dramatically reduce memory -usage. See, e.g., the tests/perf.html example. - - -
- - - - - - - - - - - -
- - -
<static> - - - Dygraph.endPan(event, g, context) - -
-
- Called in response to an interaction model operation that -responds to an event that ends panning. - -It's used in the default callback for "mouseup" operations. -Custom interaction model builders can use it to provide the default -panning behavior. - - -
- - - - -
-
Parameters:
- -
- {Event} event - -
-
the event object which led to the startZoom call.
- -
- {Dygraph} g - -
-
The dygraph on which to act.
- -
- {Object} context - -
-
The dragging context object (with -dragStartX/dragStartY/etc. properties). This function modifies the context.
- -
- - - - - - - - -
- - -
<static> - - - Dygraph.endZoom(event, g, context) - -
-
- Called in response to an interaction model operation that -responds to an event that performs a zoom based on previously defined -bounds.. - -It's used in the default callback for "mouseup" operations. -Custom interaction model builders can use it to provide the default -zooming behavior. - - -
- - - - -
-
Parameters:
- -
- {Event} event - -
-
the event object which led to the endZoom call.
- -
- {Dygraph} g - -
-
The dygraph on which to end the zoom.
- -
- {Object} context - -
-
The dragging context object (with -dragStartX/dragStartY/etc. properties). This function modifies the context.
- -
- - - - - - - - -
- - -
<static> - - {String} - Dygraph.floatFormat(x, opt_precision) - -
-
- Number formatting function which mimicks the behavior of %g in printf, i.e. -either exponential or fixed format (without trailing 0s) is used depending on -the length of the generated string. The advantage of this format is that -there is a predictable upper bound on the resulting string length, -significant figures are not dropped, and normal numbers are not displayed in -exponential notation. - -NOTE: JavaScript's native toPrecision() is NOT a drop-in replacement for %g. -It creates strings which are too long for absolute values between 10^-4 and -10^-6, e.g. '0.00001' instead of '1e-5'. See tests/number-format.html for -output examples. - - -
- - - - -
-
Parameters:
- -
- {Number} x - -
-
The number to format
- -
- {Number} opt_precision - -
-
The precision to use, default 2.
- -
- - - - - -
-
Returns:
- -
{String} A string formatted like %g in printf. The max generated - string length should be precision + 6 (e.g 1.123e+300).
- -
- - - - -
- - -
- - {Array} - getColors() - -
-
- Return the list of colors. This is either the list of colors passed in the -attributes or the autogenerated list of rgb(r,g,b) strings. - - -
- - - - - - - - -
-
Returns:
- -
{Array} The list of colors.
- -
- - - - -
- - -
- - {Integer} - getSelection() - -
-
- Returns the number of the currently selected row. To get data for this row, -you can use the getValue method. - - -
- - - - - - - - -
-
Returns:
- -
{Integer} row number, or -1 if nothing is selected
- -
- - - - -
- - -
- - {Number} - getValue(row, col) - -
-
- Returns the value in the given row and column. If the row and column exceed -the bounds on the data, returns null. Also returns null if the value is -missing. - - -
- - - - -
-
Parameters:
- -
- {Number} row - -
-
The row number of the data (0-based). Row 0 is the -first row of data, not a header row.
- -
- {Number} col - -
-
The column number of the data (0-based)
- -
- - - - - -
-
Returns:
- -
{Number} The value in the specified cell or null if the row/col -were out of range.
- -
- - - - -
- - -
<static> - - - Dygraph.GVizChart(container) - -
-
- A wrapper around Dygraph that implements the gviz API. - - -
- - - - -
-
Parameters:
- -
- {Object} container - -
-
The DOM object the visualization should live in.
- -
- - - - - - - - -
- - -
- - - indexFromSetName(name) - -
-
- Get the index of a series (column) given its name. The first column is the -x-axis, so the data series start with index 1. - - -
- - - - -
-
Parameters:
- -
- name - -
-
- -
- - - - - - - - -
- - -
- - - isZoomed(axis) - -
-
- Returns the zoomed status of the chart for one or both axes. - -Axis is an optional parameter. Can be set to 'x' or 'y'. - -The zoomed status for an axis is set whenever a user zooms using the mouse -or when the dateWindow or valueRange are updated (unless the isZoomedIgnoreProgrammaticZoom -option is also specified). - - -
- - - - -
-
Parameters:
- -
- axis - -
-
- -
- - - - - - - - -
- - -
<static> - - - Dygraph.movePan(event, g, context) - -
-
- Called in response to an interaction model operation that -responds to an event that pans the view. - -It's used in the default callback for "mousemove" operations. -Custom interaction model builders can use it to provide the default -panning behavior. - - -
- - - - -
-
Parameters:
- -
- {Event} event - -
-
the event object which led to the movePan call.
- -
- {Dygraph} g - -
-
The dygraph on which to act.
- -
- {Object} context - -
-
The dragging context object (with -dragStartX/dragStartY/etc. properties). This function modifies the context.
- -
- - - - - - - - -
- - -
<static> - - - Dygraph.moveZoom(event, g, context) - -
-
- Called in response to an interaction model operation that -responds to an event that defines zoom boundaries. - -It's used in the default callback for "mousemove" operations. -Custom interaction model builders can use it to provide the default -zooming behavior. - - -
- - - - -
-
Parameters:
- -
- {Event} event - -
-
the event object which led to the moveZoom call.
- -
- {Dygraph} g - -
-
The dygraph on which to act.
- -
- {Object} context - -
-
The dragging context object (with -dragStartX/dragStartY/etc. properties). This function modifies the context.
- -
- - - - - - - - -
- - -
- - {Number} - numAxes() - -
-
- Returns the number of y-axes on the chart. - - -
- - - - - - - - -
-
Returns:
- -
{Number} the number of axes.
- -
- - - - -
- - -
- - {Integer} - numColumns() - -
-
- Returns the number of columns (including the independent variable). - - -
- - - - - - - - -
-
Returns:
- -
{Integer} The number of columns.
- -
- - - - -
- - -
<static> - - {[Object]} - Dygraph.numericTicks(minV, maxV, self, attribute, vals) - -
-
- Add ticks when the x axis has numbers on it (instead of dates) - - -
- - - - -
-
Parameters:
- -
- {Number} minV - -
-
minimum value
- -
- {Number} maxV - -
-
maximum value
- -
- self - -
-
- -
- {function} attribute - -
-
accessor function.
- -
- vals - -
-
- -
- - - - - -
-
Returns:
- -
{[Object]} Array of {label, value} tuples.
- -
- - - - -
- - -
- - {Integer} - numRows() - -
-
- Returns the number of rows (excluding any header/label row). - - -
- - - - - - - - -
-
Returns:
- -
{Integer} The number of rows, less any header.
- -
- - - - -
- - -
- - - resize(width, height) - -
-
- Resizes the dygraph. If no parameters are specified, resizes to fill the -containing div (which has presumably changed size since the dygraph was -instantiated. If the width/height are specified, the div will be resized. - -This is far more efficient than destroying and re-instantiating a -Dygraph, since it doesn't have to reparse the underlying data. - - -
- - - - -
-
Parameters:
- -
- {Number} width - Optional -
-
Width (in pixels)
- -
- {Number} height - Optional -
-
Height (in pixels)
- -
- - - - - - - - -
- - -
- - {Number} - rollPeriod() - -
-
- Returns the current rolling period, as set by the user or an option. - - -
- - - - - - - - -
-
Returns:
- -
{Number} The number of points in the rolling window
- -
- - - - -
- - -
- - - setAnnotations(ann, suppressDraw) - -
-
- Update the list of annotations and redraw the chart. - - -
- - - - -
-
Parameters:
- -
- ann - -
-
- -
- suppressDraw - -
-
- -
- - - - - - - - -
- - -
- - - setSelection(row) - -
-
- Manually set the selected points and display information about them in the -legend. The selection can be cleared using clearSelection() and queried -using getSelection(). - - -
- - - - -
-
Parameters:
- -
- {Integer} row - -
-
number that should be highlighted (i.e. appear with -hover dots on the chart). Set to false to clear any selection.
- -
- - - - - - - - -
- - -
- - - setVisibility(num, value) - -
-
- Changes the visiblity of a series. - - -
- - - - -
-
Parameters:
- -
- num - -
-
- -
- value - -
-
- -
- - - - - - - - -
- - -
<static> - - - Dygraph.startPan(event, g, context) - -
-
- Called in response to an interaction model operation that -should start the default panning behavior. - -It's used in the default callback for "mousedown" operations. -Custom interaction model builders can use it to provide the default -panning behavior. - - -
- - - - -
-
Parameters:
- -
- {Event} event - -
-
the event object which led to the startPan call.
- -
- {Dygraph} g - -
-
The dygraph on which to act.
- -
- {Object} context - -
-
The dragging context object (with -dragStartX/dragStartY/etc. properties). This function modifies the context.
- -
- - - - - - - - -
- - -
<static> - - - Dygraph.startZoom(event, g, context) - -
-
- Called in response to an interaction model operation that -responds to an event that starts zooming. - -It's used in the default callback for "mousedown" operations. -Custom interaction model builders can use it to provide the default -zooming behavior. - - -
- - - - -
-
Parameters:
- -
- {Event} event - -
-
the event object which led to the startZoom call.
- -
- {Dygraph} g - -
-
The dygraph on which to act.
- -
- {Object} context - -
-
The dragging context object (with -dragStartX/dragStartY/etc. properties). This function modifies the context.
- -
- - - - - - - - -
- - -
- - - toDataCoords(x, y, axis) - -
-
- Convert from canvas/div coords to data coordinates. -If specified, do this conversion for the coordinate system of a particular -axis. Uses the first axis by default. -Returns a two-element array: [X, Y]. - -Note: use toDataXCoord instead of toDataCoords(x, null) and use toDataYCoord -instead of toDataCoords(null, y, axis). - - -
- - - - -
-
Parameters:
- -
- x - -
-
- -
- y - -
-
- -
- axis - -
-
- -
- - - - - - - - -
- - -
- - - toDataXCoord(x) - -
-
- Convert from canvas/div x coordinate to data coordinate. - -If x is null, this returns null. - - -
- - - - -
-
Parameters:
- -
- x - -
-
- -
- - - - - - - - -
- - -
- - - toDataYCoord(y, axis) - -
-
- Convert from canvas/div y coord to value. - -If y is null, this returns null. -if axis is null, this uses the first axis. - - -
- - - - -
-
Parameters:
- -
- y - -
-
- -
- axis - -
-
- -
- - - - - - - - -
- - -
- - - toDomCoords(x, y, axis) - -
-
- Convert from data coordinates to canvas/div X/Y coordinates. -If specified, do this conversion for the coordinate system of a particular -axis. Uses the first axis by default. -Returns a two-element array: [X, Y] - -Note: use toDomXCoord instead of toDomCoords(x, null) and use toDomYCoord -instead of toDomCoords(null, y, axis). - - -
- - - - -
-
Parameters:
- -
- x - -
-
- -
- y - -
-
- -
- axis - -
-
- -
- - - - - - - - -
- - -
- - - toDomXCoord(x) - -
-
- Convert from data x coordinates to canvas/div X coordinate. -If specified, do this conversion for the coordinate system of a particular -axis. -Returns a single value or null if x is null. - - -
- - - - -
-
Parameters:
- -
- x - -
-
- -
- - - - - - - - -
- - -
- - - toDomYCoord(y, axis) - -
-
- Convert from data x coordinates to canvas/div Y coordinate and optional -axis. Uses the first axis by default. - -returns a single value or null if y is null. - - -
- - - - -
-
Parameters:
- -
- y - -
-
- -
- axis - -
-
- -
- - - - - - - - -
- - -
- - {Number} - toPercentXCoord(x) - -
-
- Converts an x value to a percentage from the left to the right of -the drawing area. - -If the coordinate represents a value visible on the canvas, then -the value will be between 0 and 1, where 0 is the left of the canvas. -However, this method will return values outside the range, as -values can fall outside the canvas. - -If x is null, this returns null. - - -
- - - - -
-
Parameters:
- -
- {Number} x - -
-
The data x-coordinate.
- -
- - - - - -
-
Returns:
- -
{Number} A fraction in [0, 1] where 0 = the left edge.
- -
- - - - -
- - -
- - {Number} - toPercentYCoord(y, axis) - -
-
- Converts a y for an axis to a percentage from the top to the -bottom of the drawing area. - -If the coordinate represents a value visible on the canvas, then -the value will be between 0 and 1, where 0 is the top of the canvas. -However, this method will return values outside the range, as -values can fall outside the canvas. - -If y is null, this returns null. -if axis is null, this uses the first axis. - - -
- - - - -
-
Parameters:
- -
- {Number} y - -
-
The data y-coordinate.
- -
- {Number} axis - Optional -
-
The axis number on which the data coordinate lives.
- -
- - - - - -
-
Returns:
- -
{Number} A fraction in [0, 1] where 0 = the top edge.
- -
- - - - -
- - -
- - - toString() - -
-
- Returns information about the Dygraph object, including its containing ID. - - -
- - - - - - - - - - - -
- - -
<static> - - - Dygraph.toString() - -
-
- Returns information about the Dygraph class. - - -
- - - - - - - - - - - -
- - -
- - - updateOptions(attrs) - -
-
- Changes various properties of the graph. These can include: -
    -
  • file: changes the source data for the graph
  • -
  • errorBars: changes whether the data contains stddev
  • -
- - -
- - - - -
-
Parameters:
- -
- {Object} attrs - -
-
The new properties and values
- -
- - - - - - - - -
- - -
- - - visibility() - -
-
- Returns a boolean array of visibility statuses. - - -
- - - - - - - - - - - -
- - -
- - - xAxisExtremes() - -
-
- Returns the lower- and upper-bound x-axis values of the -data set. - - -
- - - - - - - - - - - -
- - -
- - - xAxisRange() - -
-
- Returns the currently-visible x-range. This can be affected by zooming, -panning or a call to updateOptions. -Returns a two-element array: [left, right]. -If the Dygraph has dates on the x-axis, these will be millis since epoch. - - -
- - - - - - - - - - - -
- - -
- - - yAxisRange(idx) - -
-
- Returns the currently-visible y-range for an axis. This can be affected by -zooming, panning or a call to updateOptions. Axis indices are zero-based. If -called with no arguments, returns the range of the first axis. -Returns a two-element array: [bottom, top]. - - -
- - - - -
-
Parameters:
- -
- idx - -
-
- -
- - - - - - - - -
- - -
- - - yAxisRanges() - -
-
- Returns the currently-visible y-ranges for each axis. This can be affected by -zooming, panning, calls to updateOptions, etc. -Returns an array of [bottom, top] pairs, one for each y-axis. - - -
- - - - - - - - - - - - - - - - - - -
-
- - - -
- - Documentation generated by JsDoc Toolkit 2.4.0 on Mon May 09 2011 07:12:49 GMT-0700 (PDT) -
- - diff --git a/jsdoc-toolkit/out/jsdoc/symbols/_global_.html b/jsdoc-toolkit/out/jsdoc/symbols/_global_.html deleted file mode 100644 index a43c452..0000000 --- a/jsdoc-toolkit/out/jsdoc/symbols/_global_.html +++ /dev/null @@ -1,251 +0,0 @@ - - - - - - - JsDoc Reference - _global_ - - - - - - - - - - - -
- -
Class Index -| File Index
-
-

Classes

- -
- -
- -
- -

- - Built-In Namespace _global_ -

- - -

- - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
- - Documentation generated by JsDoc Toolkit 2.4.0 on Mon May 09 2011 07:12:49 GMT-0700 (PDT) -
- - diff --git a/jsdoc-toolkit/out/jsdoc/symbols/src/dygraph.js.html b/jsdoc-toolkit/out/jsdoc/symbols/src/dygraph.js.html deleted file mode 100644 index e8272b5..0000000 --- a/jsdoc-toolkit/out/jsdoc/symbols/src/dygraph.js.html +++ /dev/null @@ -1,4530 +0,0 @@ -
  1 // Copyright 2006 Dan Vanderkam (danvdk@gmail.com)
-  2 // All Rights Reserved.
-  3 
-  4 /**
-  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)
- 10 
- 11   Usage:
- 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
- 16                  { }); // options
- 17    </script>
- 18 
- 19  The CSV file is of the form
- 20 
- 21    Date,SeriesA,SeriesB,SeriesC
- 22    YYYYMMDD,A1,B1,C1
- 23    YYYYMMDD,A2,B2,C2
- 24 
- 25  If the 'errorBars' option is set in the constructor, the input should be of
- 26  the form
- 27    Date,SeriesA,SeriesB,...
- 28    YYYYMMDD,A1,sigmaA1,B1,sigmaB1,...
- 29    YYYYMMDD,A2,sigmaA2,B2,sigmaB2,...
- 30 
- 31  If the 'fractions' option is set, the input should be of the form:
- 32 
- 33    Date,SeriesA,SeriesB,...
- 34    YYYYMMDD,A1/B1,A2/B2,...
- 35    YYYYMMDD,A1/B1,A2/B2,...
- 36 
- 37  And error bars will be calculated automatically using a binomial distribution.
- 38 
- 39  For further documentation and examples, see http://dygraphs.com/
- 40 
- 41  */
- 42 
- 43 /**
- 44  * Creates an interactive, zoomable chart.
- 45  *
- 46  * @constructor
- 47  * @param {div | String} div A div or the id of a div into which to construct
- 48  * the chart.
- 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.
- 56  */
- 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]);
- 65     } else {
- 66       this.__init__(div, data, opts);
- 67     }
- 68   }
- 69 };
- 70 
- 71 Dygraph.NAME = "Dygraph";
- 72 Dygraph.VERSION = "1.2";
- 73 Dygraph.__repr__ = function() {
- 74   return "[" + this.NAME + " " + this.VERSION + "]";
- 75 };
- 76 
- 77 /**
- 78  * Returns information about the Dygraph class.
- 79  */
- 80 Dygraph.toString = function() {
- 81   return this.__repr__();
- 82 };
- 83 
- 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;
- 89 
- 90 Dygraph.LOG_SCALE = 10;
- 91 Dygraph.LN_TEN = Math.log(Dygraph.LOG_SCALE);
- 92 /**
- 93  * @private
- 94  */
- 95 Dygraph.log10 = function(x) {
- 96   return Math.log(x) / Dygraph.LN_TEN;
- 97 }
- 98 
- 99 // Default attribute values.
-100 Dygraph.DEFAULT_ATTRS = {
-101   highlightCircleSize: 3,
-102   pixelsPerXLabel: 60,
-103   pixelsPerYLabel: 30,
-104 
-105   labelsDivWidth: 250,
-106   labelsDivStyles: {
-107     // TODO(danvk): move defaults from createStatusMessage_ here.
-108   },
-109   labelsSeparateLines: false,
-110   labelsShowZeroValues: true,
-111   labelsKMB: false,
-112   labelsKMG2: false,
-113   showLabelsOnHighlight: true,
-114 
-115   yValueFormatter: function(a,b) { return Dygraph.numberFormatter(a,b); },
-116   digitsAfterDecimal: 2,
-117   maxNumberWidth: 6,
-118   sigFigs: null,
-119 
-120   strokeWidth: 1.0,
-121 
-122   axisTickSize: 3,
-123   axisLabelFontSize: 14,
-124   xAxisLabelWidth: 50,
-125   yAxisLabelWidth: 50,
-126   xAxisLabelFormatter: Dygraph.dateAxisFormatter,
-127   rightGap: 5,
-128 
-129   showRoller: false,
-130   xValueFormatter: Dygraph.dateString_,
-131   xValueParser: Dygraph.dateParser,
-132   xTicker: Dygraph.dateTicker,
-133 
-134   delimiter: ',',
-135 
-136   sigma: 2.0,
-137   errorBars: false,
-138   fractions: false,
-139   wilsonInterval: true,  // only relevant if fractions is true
-140   customBars: false,
-141   fillGraph: false,
-142   fillAlpha: 0.15,
-143   connectSeparatedPoints: false,
-144 
-145   stackedGraph: false,
-146   hideOverlayOnMouseOut: true,
-147 
-148   // TODO(danvk): support 'onmouseover' and 'never', and remove synonyms.
-149   legend: 'onmouseover',  // the only relevant value at the moment is 'always'.
-150 
-151   stepPlot: false,
-152   avoidMinZero: false,
-153 
-154   // Sizes of the various chart labels.
-155   titleHeight: 28,
-156   xLabelHeight: 18,
-157   yLabelWidth: 18,
-158 
-159   interactionModel: null  // will be set to Dygraph.defaultInteractionModel.
-160 };
-161 
-162 // Various logging levels.
-163 Dygraph.DEBUG = 1;
-164 Dygraph.INFO = 2;
-165 Dygraph.WARNING = 3;
-166 Dygraph.ERROR = 3;
-167 
-168 // Directions for panning and zooming. Use bit operations when combined
-169 // values are possible.
-170 Dygraph.HORIZONTAL = 1;
-171 Dygraph.VERTICAL = 2;
-172 
-173 // Used for initializing annotation CSS rules only once.
-174 Dygraph.addedAnnotationCSS = false;
-175 
-176 /**
-177  * @private
-178  * Return the 2d context for a dygraph canvas.
-179  *
-180  * This method is only exposed for the sake of replacing the function in
-181  * automated tests, e.g.
-182  *
-183  * var oldFunc = Dygraph.getContext();
-184  * Dygraph.getContext = function(canvas) {
-185  *   var realContext = oldFunc(canvas);
-186  *   return new Proxy(realContext);
-187  * };
-188  */
-189 Dygraph.getContext = function(canvas) {
-190   return canvas.getContext("2d");
-191 };
-192 
-193 Dygraph.prototype.__old_init__ = function(div, file, labels, attrs) {
-194   // Labels is no longer a constructor parameter, since it's typically set
-195   // directly from the data source. It also conains a name for the x-axis,
-196   // which the previous constructor form did not.
-197   if (labels != null) {
-198     var new_labels = ["Date"];
-199     for (var i = 0; i < labels.length; i++) new_labels.push(labels[i]);
-200     Dygraph.update(attrs, { 'labels': new_labels });
-201   }
-202   this.__init__(div, file, attrs);
-203 };
-204 
-205 /**
-206  * Initializes the Dygraph. This creates a new DIV and constructs the PlotKit
-207  * and context <canvas> inside of it. See the constructor for details.
-208  * on the parameters.
-209  * @param {Element} div the Element to render the graph into.
-210  * @param {String | Function} file Source data
-211  * @param {Object} attrs Miscellaneous other options
-212  * @private
-213  */
-214 Dygraph.prototype.__init__ = function(div, file, attrs) {
-215   // Hack for IE: if we're using excanvas and the document hasn't finished
-216   // loading yet (and hence may not have initialized whatever it needs to
-217   // initialize), then keep calling this routine periodically until it has.
-218   if (/MSIE/.test(navigator.userAgent) && !window.opera &&
-219       typeof(G_vmlCanvasManager) != 'undefined' &&
-220       document.readyState != 'complete') {
-221     var self = this;
-222     setTimeout(function() { self.__init__(div, file, attrs) }, 100);
-223   }
-224 
-225   // Support two-argument constructor
-226   if (attrs == null) { attrs = {}; }
-227 
-228   // Copy the important bits into the object
-229   // TODO(danvk): most of these should just stay in the attrs_ dictionary.
-230   this.maindiv_ = div;
-231   this.file_ = file;
-232   this.rollPeriod_ = attrs.rollPeriod || Dygraph.DEFAULT_ROLL_PERIOD;
-233   this.previousVerticalX_ = -1;
-234   this.fractions_ = attrs.fractions || false;
-235   this.dateWindow_ = attrs.dateWindow || null;
-236 
-237   this.wilsonInterval_ = attrs.wilsonInterval || true;
-238   this.is_initial_draw_ = true;
-239   this.annotations_ = [];
-240 
-241   // Zoomed indicators - These indicate when the graph has been zoomed and on what axis.
-242   this.zoomed_x_ = false;
-243   this.zoomed_y_ = false;
-244 
-245   // Clear the div. This ensure that, if multiple dygraphs are passed the same
-246   // div, then only one will be drawn.
-247   div.innerHTML = "";
-248 
-249   // If the div isn't already sized then inherit from our attrs or
-250   // give it a default size.
-251   if (div.style.width == '') {
-252     div.style.width = (attrs.width || Dygraph.DEFAULT_WIDTH) + "px";
-253   }
-254   if (div.style.height == '') {
-255     div.style.height = (attrs.height || Dygraph.DEFAULT_HEIGHT) + "px";
-256   }
-257   this.width_ = parseInt(div.style.width, 10);
-258   this.height_ = parseInt(div.style.height, 10);
-259   // The div might have been specified as percent of the current window size,
-260   // convert that to an appropriate number of pixels.
-261   if (div.style.width.indexOf("%") == div.style.width.length - 1) {
-262     this.width_ = div.offsetWidth;
-263   }
-264   if (div.style.height.indexOf("%") == div.style.height.length - 1) {
-265     this.height_ = div.offsetHeight;
-266   }
-267 
-268   if (this.width_ == 0) {
-269     this.error("dygraph has zero width. Please specify a width in pixels.");
-270   }
-271   if (this.height_ == 0) {
-272     this.error("dygraph has zero height. Please specify a height in pixels.");
-273   }
-274 
-275   // TODO(danvk): set fillGraph to be part of attrs_ here, not user_attrs_.
-276   if (attrs['stackedGraph']) {
-277     attrs['fillGraph'] = true;
-278     // TODO(nikhilk): Add any other stackedGraph checks here.
-279   }
-280 
-281   // Dygraphs has many options, some of which interact with one another.
-282   // To keep track of everything, we maintain two sets of options:
-283   //
-284   //  this.user_attrs_   only options explicitly set by the user.
-285   //  this.attrs_        defaults, options derived from user_attrs_, data.
-286   //
-287   // Options are then accessed this.attr_('attr'), which first looks at
-288   // user_attrs_ and then computed attrs_. This way Dygraphs can set intelligent
-289   // defaults without overriding behavior that the user specifically asks for.
-290   this.user_attrs_ = {};
-291   Dygraph.update(this.user_attrs_, attrs);
-292 
-293   this.attrs_ = {};
-294   Dygraph.update(this.attrs_, Dygraph.DEFAULT_ATTRS);
-295 
-296   this.boundaryIds_ = [];
-297 
-298   // Make a note of whether labels will be pulled from the CSV file.
-299   this.labelsFromCSV_ = (this.attr_("labels") == null);
-300 
-301   // Create the containing DIV and other interactive elements
-302   this.createInterface_();
-303 
-304   this.start_();
-305 };
-306 
-307 /**
-308  * Returns the zoomed status of the chart for one or both axes.
-309  *
-310  * Axis is an optional parameter. Can be set to 'x' or 'y'.
-311  *
-312  * The zoomed status for an axis is set whenever a user zooms using the mouse
-313  * or when the dateWindow or valueRange are updated (unless the isZoomedIgnoreProgrammaticZoom
-314  * option is also specified).
-315  */
-316 Dygraph.prototype.isZoomed = function(axis) {
-317   if (axis == null) return this.zoomed_x_ || this.zoomed_y_;
-318   if (axis == 'x') return this.zoomed_x_;
-319   if (axis == 'y') return this.zoomed_y_;
-320   throw "axis parameter to Dygraph.isZoomed must be missing, 'x' or 'y'.";
-321 };
-322 
-323 /**
-324  * Returns information about the Dygraph object, including its containing ID.
-325  */
-326 Dygraph.prototype.toString = function() {
-327   var maindiv = this.maindiv_;
-328   var id = (maindiv && maindiv.id) ? maindiv.id : maindiv
-329   return "[Dygraph " + id + "]";
-330 }
-331 
-332 /**
-333  * @private
-334  * Returns the value of an option. This may be set by the user (either in the
-335  * constructor or by calling updateOptions) or by dygraphs, and may be set to a
-336  * per-series value.
-337  * @param { String } name The name of the option, e.g. 'rollPeriod'.
-338  * @param { String } [seriesName] The name of the series to which the option
-339  * will be applied. If no per-series value of this option is available, then
-340  * the global value is returned. This is optional.
-341  * @return { ... } The value of the option.
-342  */
-343 Dygraph.prototype.attr_ = function(name, seriesName) {
-344 // <REMOVE_FOR_COMBINED>
-345   if (typeof(Dygraph.OPTIONS_REFERENCE) === 'undefined') {
-346     this.error('Must include options reference JS for testing');
-347   } else if (!Dygraph.OPTIONS_REFERENCE.hasOwnProperty(name)) {
-348     this.error('Dygraphs is using property ' + name + ', which has no entry ' +
-349                'in the Dygraphs.OPTIONS_REFERENCE listing.');
-350     // Only log this error once.
-351     Dygraph.OPTIONS_REFERENCE[name] = true;
-352   }
-353 // </REMOVE_FOR_COMBINED>
-354   if (seriesName &&
-355       typeof(this.user_attrs_[seriesName]) != 'undefined' &&
-356       this.user_attrs_[seriesName] != null &&
-357       typeof(this.user_attrs_[seriesName][name]) != 'undefined') {
-358     return this.user_attrs_[seriesName][name];
-359   } else if (typeof(this.user_attrs_[name]) != 'undefined') {
-360     return this.user_attrs_[name];
-361   } else if (typeof(this.attrs_[name]) != 'undefined') {
-362     return this.attrs_[name];
-363   } else {
-364     return null;
-365   }
-366 };
-367 
-368 // TODO(danvk): any way I can get the line numbers to be this.warn call?
-369 /**
-370  * @private
-371  * Log an error on the JS console at the given severity.
-372  * @param { Integer } severity One of Dygraph.{DEBUG,INFO,WARNING,ERROR}
-373  * @param { String } The message to log.
-374  */
-375 Dygraph.prototype.log = function(severity, message) {
-376   if (typeof(console) != 'undefined') {
-377     switch (severity) {
-378       case Dygraph.DEBUG:
-379         console.debug('dygraphs: ' + message);
-380         break;
-381       case Dygraph.INFO:
-382         console.info('dygraphs: ' + message);
-383         break;
-384       case Dygraph.WARNING:
-385         console.warn('dygraphs: ' + message);
-386         break;
-387       case Dygraph.ERROR:
-388         console.error('dygraphs: ' + message);
-389         break;
-390     }
-391   }
-392 };
-393 
-394 /**
-395  * @private
-396  */
-397 Dygraph.prototype.info = function(message) {
-398   this.log(Dygraph.INFO, message);
-399 };
-400 
-401 /**
-402  * @private
-403  */
-404 Dygraph.prototype.warn = function(message) {
-405   this.log(Dygraph.WARNING, message);
-406 };
-407 
-408 /**
-409  * @private
-410  */
-411 Dygraph.prototype.error = function(message) {
-412   this.log(Dygraph.ERROR, message);
-413 };
-414 
-415 /**
-416  * Returns the current rolling period, as set by the user or an option.
-417  * @return {Number} The number of points in the rolling window
-418  */
-419 Dygraph.prototype.rollPeriod = function() {
-420   return this.rollPeriod_;
-421 };
-422 
-423 /**
-424  * Returns the currently-visible x-range. This can be affected by zooming,
-425  * panning or a call to updateOptions.
-426  * Returns a two-element array: [left, right].
-427  * If the Dygraph has dates on the x-axis, these will be millis since epoch.
-428  */
-429 Dygraph.prototype.xAxisRange = function() {
-430   return this.dateWindow_ ? this.dateWindow_ : this.xAxisExtremes();
-431 };
-432 
-433 /**
-434  * Returns the lower- and upper-bound x-axis values of the
-435  * data set.
-436  */
-437 Dygraph.prototype.xAxisExtremes = function() {
-438   var left = this.rawData_[0][0];
-439   var right = this.rawData_[this.rawData_.length - 1][0];
-440   return [left, right];
-441 };
-442 
-443 /**
-444  * Returns the currently-visible y-range for an axis. This can be affected by
-445  * zooming, panning or a call to updateOptions. Axis indices are zero-based. If
-446  * called with no arguments, returns the range of the first axis.
-447  * Returns a two-element array: [bottom, top].
-448  */
-449 Dygraph.prototype.yAxisRange = function(idx) {
-450   if (typeof(idx) == "undefined") idx = 0;
-451   if (idx < 0 || idx >= this.axes_.length) return null;
-452   return [ this.axes_[idx].computedValueRange[0],
-453            this.axes_[idx].computedValueRange[1] ];
-454 };
-455 
-456 /**
-457  * Returns the currently-visible y-ranges for each axis. This can be affected by
-458  * zooming, panning, calls to updateOptions, etc.
-459  * Returns an array of [bottom, top] pairs, one for each y-axis.
-460  */
-461 Dygraph.prototype.yAxisRanges = function() {
-462   var ret = [];
-463   for (var i = 0; i < this.axes_.length; i++) {
-464     ret.push(this.yAxisRange(i));
-465   }
-466   return ret;
-467 };
-468 
-469 // TODO(danvk): use these functions throughout dygraphs.
-470 /**
-471  * Convert from data coordinates to canvas/div X/Y coordinates.
-472  * If specified, do this conversion for the coordinate system of a particular
-473  * axis. Uses the first axis by default.
-474  * Returns a two-element array: [X, Y]
-475  *
-476  * Note: use toDomXCoord instead of toDomCoords(x, null) and use toDomYCoord
-477  * instead of toDomCoords(null, y, axis).
-478  */
-479 Dygraph.prototype.toDomCoords = function(x, y, axis) {
-480   return [ this.toDomXCoord(x), this.toDomYCoord(y, axis) ];
-481 };
-482 
-483 /**
-484  * Convert from data x coordinates to canvas/div X coordinate.
-485  * If specified, do this conversion for the coordinate system of a particular
-486  * axis.
-487  * Returns a single value or null if x is null.
-488  */
-489 Dygraph.prototype.toDomXCoord = function(x) {
-490   if (x == null) {
-491     return null;
-492   };
-493 
-494   var area = this.plotter_.area;
-495   var xRange = this.xAxisRange();
-496   return area.x + (x - xRange[0]) / (xRange[1] - xRange[0]) * area.w;
-497 }
-498 
-499 /**
-500  * Convert from data x coordinates to canvas/div Y coordinate and optional
-501  * axis. Uses the first axis by default.
-502  *
-503  * returns a single value or null if y is null.
-504  */
-505 Dygraph.prototype.toDomYCoord = function(y, axis) {
-506   var pct = this.toPercentYCoord(y, axis);
-507 
-508   if (pct == null) {
-509     return null;
-510   }
-511   var area = this.plotter_.area;
-512   return area.y + pct * area.h;
-513 }
-514 
-515 /**
-516  * Convert from canvas/div coords to data coordinates.
-517  * If specified, do this conversion for the coordinate system of a particular
-518  * axis. Uses the first axis by default.
-519  * Returns a two-element array: [X, Y].
-520  *
-521  * Note: use toDataXCoord instead of toDataCoords(x, null) and use toDataYCoord
-522  * instead of toDataCoords(null, y, axis).
-523  */
-524 Dygraph.prototype.toDataCoords = function(x, y, axis) {
-525   return [ this.toDataXCoord(x), this.toDataYCoord(y, axis) ];
-526 };
-527 
-528 /**
-529  * Convert from canvas/div x coordinate to data coordinate.
-530  *
-531  * If x is null, this returns null.
-532  */
-533 Dygraph.prototype.toDataXCoord = function(x) {
-534   if (x == null) {
-535     return null;
-536   }
-537 
-538   var area = this.plotter_.area;
-539   var xRange = this.xAxisRange();
-540   return xRange[0] + (x - area.x) / area.w * (xRange[1] - xRange[0]);
-541 };
-542 
-543 /**
-544  * Convert from canvas/div y coord to value.
-545  *
-546  * If y is null, this returns null.
-547  * if axis is null, this uses the first axis.
-548  */
-549 Dygraph.prototype.toDataYCoord = function(y, axis) {
-550   if (y == null) {
-551     return null;
-552   }
-553 
-554   var area = this.plotter_.area;
-555   var yRange = this.yAxisRange(axis);
-556 
-557   if (typeof(axis) == "undefined") axis = 0;
-558   if (!this.axes_[axis].logscale) {
-559     return yRange[0] + (area.h - y) / area.h * (yRange[1] - yRange[0]);
-560   } else {
-561     // Computing the inverse of toDomCoord.
-562     var pct = (y - area.y) / area.h
-563 
-564     // Computing the inverse of toPercentYCoord. The function was arrived at with
-565     // the following steps:
-566     //
-567     // Original calcuation:
-568     // pct = (logr1 - Dygraph.log10(y)) / (logr1 - Dygraph.log10(yRange[0]));
-569     //
-570     // Move denominator to both sides:
-571     // pct * (logr1 - Dygraph.log10(yRange[0])) = logr1 - Dygraph.log10(y);
-572     //
-573     // subtract logr1, and take the negative value.
-574     // logr1 - (pct * (logr1 - Dygraph.log10(yRange[0]))) = Dygraph.log10(y);
-575     //
-576     // Swap both sides of the equation, and we can compute the log of the
-577     // return value. Which means we just need to use that as the exponent in
-578     // e^exponent.
-579     // Dygraph.log10(y) = logr1 - (pct * (logr1 - Dygraph.log10(yRange[0])));
-580 
-581     var logr1 = Dygraph.log10(yRange[1]);
-582     var exponent = logr1 - (pct * (logr1 - Dygraph.log10(yRange[0])));
-583     var value = Math.pow(Dygraph.LOG_SCALE, exponent);
-584     return value;
-585   }
-586 };
-587 
-588 /**
-589  * Converts a y for an axis to a percentage from the top to the
-590  * bottom of the drawing area.
-591  *
-592  * If the coordinate represents a value visible on the canvas, then
-593  * the value will be between 0 and 1, where 0 is the top of the canvas.
-594  * However, this method will return values outside the range, as
-595  * values can fall outside the canvas.
-596  *
-597  * If y is null, this returns null.
-598  * if axis is null, this uses the first axis.
-599  *
-600  * @param { Number } y The data y-coordinate.
-601  * @param { Number } [axis] The axis number on which the data coordinate lives.
-602  * @return { Number } A fraction in [0, 1] where 0 = the top edge.
-603  */
-604 Dygraph.prototype.toPercentYCoord = function(y, axis) {
-605   if (y == null) {
-606     return null;
-607   }
-608   if (typeof(axis) == "undefined") axis = 0;
-609 
-610   var area = this.plotter_.area;
-611   var yRange = this.yAxisRange(axis);
-612 
-613   var pct;
-614   if (!this.axes_[axis].logscale) {
-615     // yRange[1] - y is unit distance from the bottom.
-616     // yRange[1] - yRange[0] is the scale of the range.
-617     // (yRange[1] - y) / (yRange[1] - yRange[0]) is the % from the bottom.
-618     pct = (yRange[1] - y) / (yRange[1] - yRange[0]);
-619   } else {
-620     var logr1 = Dygraph.log10(yRange[1]);
-621     pct = (logr1 - Dygraph.log10(y)) / (logr1 - Dygraph.log10(yRange[0]));
-622   }
-623   return pct;
-624 }
-625 
-626 /**
-627  * Converts an x value to a percentage from the left to the right of
-628  * the drawing area.
-629  *
-630  * If the coordinate represents a value visible on the canvas, then
-631  * the value will be between 0 and 1, where 0 is the left of the canvas.
-632  * However, this method will return values outside the range, as
-633  * values can fall outside the canvas.
-634  *
-635  * If x is null, this returns null.
-636  * @param { Number } x The data x-coordinate.
-637  * @return { Number } A fraction in [0, 1] where 0 = the left edge.
-638  */
-639 Dygraph.prototype.toPercentXCoord = function(x) {
-640   if (x == null) {
-641     return null;
-642   }
-643 
-644   var xRange = this.xAxisRange();
-645   return (x - xRange[0]) / (xRange[1] - xRange[0]);
-646 };
-647 
-648 /**
-649  * Returns the number of columns (including the independent variable).
-650  * @return { Integer } The number of columns.
-651  */
-652 Dygraph.prototype.numColumns = function() {
-653   return this.rawData_[0].length;
-654 };
-655 
-656 /**
-657  * Returns the number of rows (excluding any header/label row).
-658  * @return { Integer } The number of rows, less any header.
-659  */
-660 Dygraph.prototype.numRows = function() {
-661   return this.rawData_.length;
-662 };
-663 
-664 /**
-665  * Returns the value in the given row and column. If the row and column exceed
-666  * the bounds on the data, returns null. Also returns null if the value is
-667  * missing.
-668  * @param { Number} row The row number of the data (0-based). Row 0 is the
-669  * first row of data, not a header row.
-670  * @param { Number} col The column number of the data (0-based)
-671  * @return { Number } The value in the specified cell or null if the row/col
-672  * were out of range.
-673  */
-674 Dygraph.prototype.getValue = function(row, col) {
-675   if (row < 0 || row > this.rawData_.length) return null;
-676   if (col < 0 || col > this.rawData_[row].length) return null;
-677 
-678   return this.rawData_[row][col];
-679 };
-680 
-681 /**
-682  * @private
-683  * Add an event handler. This smooths a difference between IE and the rest of
-684  * the world.
-685  * @param { DOM element } el The element to add the event to.
-686  * @param { String } evt The name of the event, e.g. 'click' or 'mousemove'.
-687  * @param { Function } fn The function to call on the event. The function takes
-688  * one parameter: the event object.
-689  */
-690 Dygraph.addEvent = function(el, evt, fn) {
-691   var normed_fn = function(e) {
-692     if (!e) var e = window.event;
-693     fn(e);
-694   };
-695   if (window.addEventListener) {  // Mozilla, Netscape, Firefox
-696     el.addEventListener(evt, normed_fn, false);
-697   } else {  // IE
-698     el.attachEvent('on' + evt, normed_fn);
-699   }
-700 };
-701 
-702 
-703 /**
-704  * @private
-705  * Cancels further processing of an event. This is useful to prevent default
-706  * browser actions, e.g. highlighting text on a double-click.
-707  * Based on the article at
-708  * http://www.switchonthecode.com/tutorials/javascript-tutorial-the-scroll-wheel
-709  * @param { Event } e The event whose normal behavior should be canceled.
-710  */
-711 Dygraph.cancelEvent = function(e) {
-712   e = e ? e : window.event;
-713   if (e.stopPropagation) {
-714     e.stopPropagation();
-715   }
-716   if (e.preventDefault) {
-717     e.preventDefault();
-718   }
-719   e.cancelBubble = true;
-720   e.cancel = true;
-721   e.returnValue = false;
-722   return false;
-723 };
-724 
-725 
-726 /**
-727  * Generates interface elements for the Dygraph: a containing div, a div to
-728  * display the current point, and a textbox to adjust the rolling average
-729  * period. Also creates the Renderer/Layout elements.
-730  * @private
-731  */
-732 Dygraph.prototype.createInterface_ = function() {
-733   // Create the all-enclosing graph div
-734   var enclosing = this.maindiv_;
-735 
-736   this.graphDiv = document.createElement("div");
-737   this.graphDiv.style.width = this.width_ + "px";
-738   this.graphDiv.style.height = this.height_ + "px";
-739   enclosing.appendChild(this.graphDiv);
-740 
-741   // Create the canvas for interactive parts of the chart.
-742   this.canvas_ = Dygraph.createCanvas();
-743   this.canvas_.style.position = "absolute";
-744   this.canvas_.width = this.width_;
-745   this.canvas_.height = this.height_;
-746   this.canvas_.style.width = this.width_ + "px";    // for IE
-747   this.canvas_.style.height = this.height_ + "px";  // for IE
-748 
-749   this.canvas_ctx_ = Dygraph.getContext(this.canvas_);
-750 
-751   // ... and for static parts of the chart.
-752   this.hidden_ = this.createPlotKitCanvas_(this.canvas_);
-753   this.hidden_ctx_ = Dygraph.getContext(this.hidden_);
-754 
-755   // The interactive parts of the graph are drawn on top of the chart.
-756   this.graphDiv.appendChild(this.hidden_);
-757   this.graphDiv.appendChild(this.canvas_);
-758   this.mouseEventElement_ = this.canvas_;
-759 
-760   var dygraph = this;
-761   Dygraph.addEvent(this.mouseEventElement_, 'mousemove', function(e) {
-762     dygraph.mouseMove_(e);
-763   });
-764   Dygraph.addEvent(this.mouseEventElement_, 'mouseout', function(e) {
-765     dygraph.mouseOut_(e);
-766   });
-767 
-768   // Create the grapher
-769   // TODO(danvk): why does the Layout need its own set of options?
-770   this.layoutOptions_ = { 'xOriginIsZero': false };
-771   Dygraph.update(this.layoutOptions_, this.attrs_);
-772   Dygraph.update(this.layoutOptions_, this.user_attrs_);
-773   Dygraph.update(this.layoutOptions_, {
-774     'errorBars': (this.attr_("errorBars") || this.attr_("customBars")) });
-775 
-776   this.layout_ = new DygraphLayout(this, this.layoutOptions_);
-777 
-778   // TODO(danvk): why does the Renderer need its own set of options?
-779   this.renderOptions_ = { colorScheme: this.colors_,
-780                           strokeColor: null,
-781                           axisLineWidth: Dygraph.AXIS_LINE_WIDTH };
-782   Dygraph.update(this.renderOptions_, this.attrs_);
-783   Dygraph.update(this.renderOptions_, this.user_attrs_);
-784 
-785   this.createStatusMessage_();
-786   this.createDragInterface_();
-787 };
-788 
-789 /**
-790  * Detach DOM elements in the dygraph and null out all data references.
-791  * Calling this when you're done with a dygraph can dramatically reduce memory
-792  * usage. See, e.g., the tests/perf.html example.
-793  */
-794 Dygraph.prototype.destroy = function() {
-795   var removeRecursive = function(node) {
-796     while (node.hasChildNodes()) {
-797       removeRecursive(node.firstChild);
-798       node.removeChild(node.firstChild);
-799     }
-800   };
-801   removeRecursive(this.maindiv_);
-802 
-803   var nullOut = function(obj) {
-804     for (var n in obj) {
-805       if (typeof(obj[n]) === 'object') {
-806         obj[n] = null;
-807       }
-808     }
-809   };
-810 
-811   // These may not all be necessary, but it can't hurt...
-812   nullOut(this.layout_);
-813   nullOut(this.plotter_);
-814   nullOut(this);
-815 };
-816 
-817 /**
-818  * Creates the canvas on which the chart will be drawn. Only the Renderer ever
-819  * draws on this particular canvas. All Dygraph work (i.e. drawing hover dots
-820  * or the zoom rectangles) is done on this.canvas_.
-821  * @param {Object} canvas The Dygraph canvas over which to overlay the plot
-822  * @return {Object} The newly-created canvas
-823  * @private
-824  */
-825 Dygraph.prototype.createPlotKitCanvas_ = function(canvas) {
-826   var h = Dygraph.createCanvas();
-827   h.style.position = "absolute";
-828   // TODO(danvk): h should be offset from canvas. canvas needs to include
-829   // some extra area to make it easier to zoom in on the far left and far
-830   // right. h needs to be precisely the plot area, so that clipping occurs.
-831   h.style.top = canvas.style.top;
-832   h.style.left = canvas.style.left;
-833   h.width = this.width_;
-834   h.height = this.height_;
-835   h.style.width = this.width_ + "px";    // for IE
-836   h.style.height = this.height_ + "px";  // for IE
-837   return h;
-838 };
-839 
-840 /**
-841  * Convert hsv values to an rgb(r,g,b) string. Taken from MochiKit.Color. This
-842  * is used to generate default series colors which are evenly spaced on the
-843  * color wheel.
-844  * @param { Number } hue Range is 0.0-1.0.
-845  * @param { Number } saturation Range is 0.0-1.0.
-846  * @param { Number } value Range is 0.0-1.0.
-847  * @return { String } "rgb(r,g,b)" where r, g and b range from 0-255.
-848  * @private
-849  */
-850 Dygraph.hsvToRGB = function (hue, saturation, value) {
-851   var red;
-852   var green;
-853   var blue;
-854   if (saturation === 0) {
-855     red = value;
-856     green = value;
-857     blue = value;
-858   } else {
-859     var i = Math.floor(hue * 6);
-860     var f = (hue * 6) - i;
-861     var p = value * (1 - saturation);
-862     var q = value * (1 - (saturation * f));
-863     var t = value * (1 - (saturation * (1 - f)));
-864     switch (i) {
-865       case 1: red = q; green = value; blue = p; break;
-866       case 2: red = p; green = value; blue = t; break;
-867       case 3: red = p; green = q; blue = value; break;
-868       case 4: red = t; green = p; blue = value; break;
-869       case 5: red = value; green = p; blue = q; break;
-870       case 6: // fall through
-871       case 0: red = value; green = t; blue = p; break;
-872     }
-873   }
-874   red = Math.floor(255 * red + 0.5);
-875   green = Math.floor(255 * green + 0.5);
-876   blue = Math.floor(255 * blue + 0.5);
-877   return 'rgb(' + red + ',' + green + ',' + blue + ')';
-878 };
-879 
-880 
-881 /**
-882  * Generate a set of distinct colors for the data series. This is done with a
-883  * color wheel. Saturation/Value are customizable, and the hue is
-884  * equally-spaced around the color wheel. If a custom set of colors is
-885  * specified, that is used instead.
-886  * @private
-887  */
-888 Dygraph.prototype.setColors_ = function() {
-889   // TODO(danvk): compute this directly into this.attrs_['colorScheme'] and do
-890   // away with this.renderOptions_.
-891   var num = this.attr_("labels").length - 1;
-892   this.colors_ = [];
-893   var colors = this.attr_('colors');
-894   if (!colors) {
-895     var sat = this.attr_('colorSaturation') || 1.0;
-896     var val = this.attr_('colorValue') || 0.5;
-897     var half = Math.ceil(num / 2);
-898     for (var i = 1; i <= num; i++) {
-899       if (!this.visibility()[i-1]) continue;
-900       // alternate colors for high contrast.
-901       var idx = i % 2 ? Math.ceil(i / 2) : (half + i / 2);
-902       var hue = (1.0 * idx/ (1 + num));
-903       this.colors_.push(Dygraph.hsvToRGB(hue, sat, val));
-904     }
-905   } else {
-906     for (var i = 0; i < num; i++) {
-907       if (!this.visibility()[i]) continue;
-908       var colorStr = colors[i % colors.length];
-909       this.colors_.push(colorStr);
-910     }
-911   }
-912 
-913   // TODO(danvk): update this w/r/t/ the new options system.
-914   this.renderOptions_.colorScheme = this.colors_;
-915   Dygraph.update(this.plotter_.options, this.renderOptions_);
-916   Dygraph.update(this.layoutOptions_, this.user_attrs_);
-917   Dygraph.update(this.layoutOptions_, this.attrs_);
-918 };
-919 
-920 /**
-921  * Return the list of colors. This is either the list of colors passed in the
-922  * attributes or the autogenerated list of rgb(r,g,b) strings.
-923  * @return {Array<string>} The list of colors.
-924  */
-925 Dygraph.prototype.getColors = function() {
-926   return this.colors_;
-927 };
-928 
-929 // The following functions are from quirksmode.org with a modification for Safari from
-930 // http://blog.firetree.net/2005/07/04/javascript-find-position/
-931 // http://www.quirksmode.org/js/findpos.html
-932 
-933 /**
-934  * @private
-935  */
-936 Dygraph.findPosX = function(obj) {
-937   var curleft = 0;
-938   if(obj.offsetParent)
-939     while(1)
-940     {
-941       curleft += obj.offsetLeft;
-942       if(!obj.offsetParent)
-943         break;
-944       obj = obj.offsetParent;
-945     }
-946   else if(obj.x)
-947     curleft += obj.x;
-948   return curleft;
-949 };
-950 
-951 
-952 /**
-953  * @private
-954  */
-955 Dygraph.findPosY = function(obj) {
-956   var curtop = 0;
-957   if(obj.offsetParent)
-958     while(1)
-959     {
-960       curtop += obj.offsetTop;
-961       if(!obj.offsetParent)
-962         break;
-963       obj = obj.offsetParent;
-964     }
-965   else if(obj.y)
-966     curtop += obj.y;
-967   return curtop;
-968 };
-969 
-970 
-971 /**
-972  * Create the div that contains information on the selected point(s)
-973  * This goes in the top right of the canvas, unless an external div has already
-974  * been specified.
-975  * @private
-976  */
-977 Dygraph.prototype.createStatusMessage_ = function() {
-978   var userLabelsDiv = this.user_attrs_["labelsDiv"];
-979   if (userLabelsDiv && null != userLabelsDiv
-980     && (typeof(userLabelsDiv) == "string" || userLabelsDiv instanceof String)) {
-981     this.user_attrs_["labelsDiv"] = document.getElementById(userLabelsDiv);
-982   }
-983   if (!this.attr_("labelsDiv")) {
-984     var divWidth = this.attr_('labelsDivWidth');
-985     var messagestyle = {
-986       "position": "absolute",
-987       "fontSize": "14px",
-988       "zIndex": 10,
-989       "width": divWidth + "px",
-990       "top": "0px",
-991       "left": (this.width_ - divWidth - 2) + "px",
-992       "background": "white",
-993       "textAlign": "left",
-994       "overflow": "hidden"};
-995     Dygraph.update(messagestyle, this.attr_('labelsDivStyles'));
-996     var div = document.createElement("div");
-997     for (var name in messagestyle) {
-998       if (messagestyle.hasOwnProperty(name)) {
-999         div.style[name] = messagestyle[name];
-1000       }
-1001     }
-1002     this.graphDiv.appendChild(div);
-1003     this.attrs_.labelsDiv = div;
-1004   }
-1005 };
-1006 
-1007 /**
-1008  * Position the labels div so that:
-1009  * - its right edge is flush with the right edge of the charting area
-1010  * - its top edge is flush with the top edge of the charting area
-1011  * @private
-1012  */
-1013 Dygraph.prototype.positionLabelsDiv_ = function() {
-1014   // Don't touch a user-specified labelsDiv.
-1015   if (this.user_attrs_.hasOwnProperty("labelsDiv")) return;
-1016 
-1017   var area = this.plotter_.area;
-1018   var div = this.attr_("labelsDiv");
-1019   div.style.left = area.x + area.w - this.attr_("labelsDivWidth") - 1 + "px";
-1020   div.style.top = area.y + "px";
-1021 };
-1022 
-1023 /**
-1024  * Create the text box to adjust the averaging period
-1025  * @private
-1026  */
-1027 Dygraph.prototype.createRollInterface_ = function() {
-1028   // Create a roller if one doesn't exist already.
-1029   if (!this.roller_) {
-1030     this.roller_ = document.createElement("input");
-1031     this.roller_.type = "text";
-1032     this.roller_.style.display = "none";
-1033     this.graphDiv.appendChild(this.roller_);
-1034   }
-1035 
-1036   var display = this.attr_('showRoller') ? 'block' : 'none';
-1037 
-1038   var area = this.plotter_.area;
-1039   var textAttr = { "position": "absolute",
-1040                    "zIndex": 10,
-1041                    "top": (area.y + area.h - 25) + "px",
-1042                    "left": (area.x + 1) + "px",
-1043                    "display": display
-1044                   };
-1045   this.roller_.size = "2";
-1046   this.roller_.value = this.rollPeriod_;
-1047   for (var name in textAttr) {
-1048     if (textAttr.hasOwnProperty(name)) {
-1049       this.roller_.style[name] = textAttr[name];
-1050     }
-1051   }
-1052 
-1053   var dygraph = this;
-1054   this.roller_.onchange = function() { dygraph.adjustRoll(dygraph.roller_.value); };
-1055 };
-1056 
-1057 /**
-1058  * @private
-1059  * Returns the x-coordinate of the event in a coordinate system where the
-1060  * top-left corner of the page (not the window) is (0,0).
-1061  * Taken from MochiKit.Signal
-1062  */
-1063 Dygraph.pageX = function(e) {
-1064   if (e.pageX) {
-1065     return (!e.pageX || e.pageX < 0) ? 0 : e.pageX;
-1066   } else {
-1067     var de = document;
-1068     var b = document.body;
-1069     return e.clientX +
-1070         (de.scrollLeft || b.scrollLeft) -
-1071         (de.clientLeft || 0);
-1072   }
-1073 };
-1074 
-1075 /**
-1076  * @private
-1077  * Returns the y-coordinate of the event in a coordinate system where the
-1078  * top-left corner of the page (not the window) is (0,0).
-1079  * Taken from MochiKit.Signal
-1080  */
-1081 Dygraph.pageY = function(e) {
-1082   if (e.pageY) {
-1083     return (!e.pageY || e.pageY < 0) ? 0 : e.pageY;
-1084   } else {
-1085     var de = document;
-1086     var b = document.body;
-1087     return e.clientY +
-1088         (de.scrollTop || b.scrollTop) -
-1089         (de.clientTop || 0);
-1090   }
-1091 };
-1092 
-1093 /**
-1094  * @private
-1095  * Converts page the x-coordinate of the event to pixel x-coordinates on the
-1096  * canvas (i.e. DOM Coords).
-1097  */
-1098 Dygraph.prototype.dragGetX_ = function(e, context) {
-1099   return Dygraph.pageX(e) - context.px
-1100 };
-1101 
-1102 /**
-1103  * @private
-1104  * Converts page the y-coordinate of the event to pixel y-coordinates on the
-1105  * canvas (i.e. DOM Coords).
-1106  */
-1107 Dygraph.prototype.dragGetY_ = function(e, context) {
-1108   return Dygraph.pageY(e) - context.py
-1109 };
-1110 
-1111 /**
-1112  * Called in response to an interaction model operation that
-1113  * should start the default panning behavior.
-1114  *
-1115  * It's used in the default callback for "mousedown" operations.
-1116  * Custom interaction model builders can use it to provide the default
-1117  * panning behavior.
-1118  *
-1119  * @param { Event } event the event object which led to the startPan call.
-1120  * @param { Dygraph} g The dygraph on which to act.
-1121  * @param { Object} context The dragging context object (with
-1122  * dragStartX/dragStartY/etc. properties). This function modifies the context.
-1123  */
-1124 Dygraph.startPan = function(event, g, context) {
-1125   context.isPanning = true;
-1126   var xRange = g.xAxisRange();
-1127   context.dateRange = xRange[1] - xRange[0];
-1128   context.initialLeftmostDate = xRange[0];
-1129   context.xUnitsPerPixel = context.dateRange / (g.plotter_.area.w - 1);
-1130 
-1131   if (g.attr_("panEdgeFraction")) {
-1132     var maxXPixelsToDraw = g.width_ * g.attr_("panEdgeFraction");
-1133     var xExtremes = g.xAxisExtremes(); // I REALLY WANT TO CALL THIS xTremes!
-1134 
-1135     var boundedLeftX = g.toDomXCoord(xExtremes[0]) - maxXPixelsToDraw;
-1136     var boundedRightX = g.toDomXCoord(xExtremes[1]) + maxXPixelsToDraw;
-1137 
-1138     var boundedLeftDate = g.toDataXCoord(boundedLeftX);
-1139     var boundedRightDate = g.toDataXCoord(boundedRightX);
-1140     context.boundedDates = [boundedLeftDate, boundedRightDate];
-1141 
-1142     var boundedValues = [];
-1143     var maxYPixelsToDraw = g.height_ * g.attr_("panEdgeFraction");
-1144 
-1145     for (var i = 0; i < g.axes_.length; i++) {
-1146       var axis = g.axes_[i];
-1147       var yExtremes = axis.extremeRange;
-1148 
-1149       var boundedTopY = g.toDomYCoord(yExtremes[0], i) + maxYPixelsToDraw;
-1150       var boundedBottomY = g.toDomYCoord(yExtremes[1], i) - maxYPixelsToDraw;
-1151 
-1152       var boundedTopValue = g.toDataYCoord(boundedTopY);
-1153       var boundedBottomValue = g.toDataYCoord(boundedBottomY);
-1154 
-1155       boundedValues[i] = [boundedTopValue, boundedBottomValue];
-1156     }
-1157     context.boundedValues = boundedValues;
-1158   }
-1159 
-1160   // Record the range of each y-axis at the start of the drag.
-1161   // If any axis has a valueRange or valueWindow, then we want a 2D pan.
-1162   context.is2DPan = false;
-1163   for (var i = 0; i < g.axes_.length; i++) {
-1164     var axis = g.axes_[i];
-1165     var yRange = g.yAxisRange(i);
-1166     // TODO(konigsberg): These values should be in |context|.
-1167     // In log scale, initialTopValue, dragValueRange and unitsPerPixel are log scale.
-1168     if (axis.logscale) {
-1169       axis.initialTopValue = Dygraph.log10(yRange[1]);
-1170       axis.dragValueRange = Dygraph.log10(yRange[1]) - Dygraph.log10(yRange[0]);
-1171     } else {
-1172       axis.initialTopValue = yRange[1];
-1173       axis.dragValueRange = yRange[1] - yRange[0];
-1174     }
-1175     axis.unitsPerPixel = axis.dragValueRange / (g.plotter_.area.h - 1);
-1176 
-1177     // While calculating axes, set 2dpan.
-1178     if (axis.valueWindow || axis.valueRange) context.is2DPan = true;
-1179   }
-1180 };
-1181 
-1182 /**
-1183  * Called in response to an interaction model operation that
-1184  * responds to an event that pans the view.
-1185  *
-1186  * It's used in the default callback for "mousemove" operations.
-1187  * Custom interaction model builders can use it to provide the default
-1188  * panning behavior.
-1189  *
-1190  * @param { Event } event the event object which led to the movePan call.
-1191  * @param { Dygraph} g The dygraph on which to act.
-1192  * @param { Object} context The dragging context object (with
-1193  * dragStartX/dragStartY/etc. properties). This function modifies the context.
-1194  */
-1195 Dygraph.movePan = function(event, g, context) {
-1196   context.dragEndX = g.dragGetX_(event, context);
-1197   context.dragEndY = g.dragGetY_(event, context);
-1198 
-1199   var minDate = context.initialLeftmostDate -
-1200     (context.dragEndX - context.dragStartX) * context.xUnitsPerPixel;
-1201   if (context.boundedDates) {
-1202     minDate = Math.max(minDate, context.boundedDates[0]);
-1203   }
-1204   var maxDate = minDate + context.dateRange;
-1205   if (context.boundedDates) {
-1206     if (maxDate > context.boundedDates[1]) {
-1207       // Adjust minDate, and recompute maxDate.
-1208       minDate = minDate - (maxDate - context.boundedDates[1]);
-1209       maxDate = minDate + context.dateRange;
-1210     }
-1211   }
-1212 
-1213   g.dateWindow_ = [minDate, maxDate];
-1214 
-1215   // y-axis scaling is automatic unless this is a full 2D pan.
-1216   if (context.is2DPan) {
-1217     // Adjust each axis appropriately.
-1218     for (var i = 0; i < g.axes_.length; i++) {
-1219       var axis = g.axes_[i];
-1220 
-1221       var pixelsDragged = context.dragEndY - context.dragStartY;
-1222       var unitsDragged = pixelsDragged * axis.unitsPerPixel;
-1223  
-1224       var boundedValue = context.boundedValues ? context.boundedValues[i] : null;
-1225 
-1226       // In log scale, maxValue and minValue are the logs of those values.
-1227       var maxValue = axis.initialTopValue + unitsDragged;
-1228       if (boundedValue) {
-1229         maxValue = Math.min(maxValue, boundedValue[1]);
-1230       }
-1231       var minValue = maxValue - axis.dragValueRange;
-1232       if (boundedValue) {
-1233         if (minValue < boundedValue[0]) {
-1234           // Adjust maxValue, and recompute minValue.
-1235           maxValue = maxValue - (minValue - boundedValue[0]);
-1236           minValue = maxValue - axis.dragValueRange;
-1237         }
-1238       }
-1239       if (axis.logscale) {
-1240         axis.valueWindow = [ Math.pow(Dygraph.LOG_SCALE, minValue),
-1241                              Math.pow(Dygraph.LOG_SCALE, maxValue) ];
-1242       } else {
-1243         axis.valueWindow = [ minValue, maxValue ];
-1244       }
-1245     }
-1246   }
-1247 
-1248   g.drawGraph_();
-1249 };
-1250 
-1251 /**
-1252  * Called in response to an interaction model operation that
-1253  * responds to an event that ends panning.
-1254  *
-1255  * It's used in the default callback for "mouseup" operations.
-1256  * Custom interaction model builders can use it to provide the default
-1257  * panning behavior.
-1258  *
-1259  * @param { Event } event the event object which led to the startZoom call.
-1260  * @param { Dygraph} g The dygraph on which to act.
-1261  * @param { Object} context The dragging context object (with
-1262  * dragStartX/dragStartY/etc. properties). This function modifies the context.
-1263  */
-1264 Dygraph.endPan = function(event, g, context) {
-1265   // TODO(konigsberg): Clear the context data from the axis.
-1266   // TODO(konigsberg): mouseup should just delete the
-1267   // context object, and mousedown should create a new one.
-1268   context.isPanning = false;
-1269   context.is2DPan = false;
-1270   context.initialLeftmostDate = null;
-1271   context.dateRange = null;
-1272   context.valueRange = null;
-1273   context.boundedDates = null;
-1274   context.boundedValues = null;
-1275 };
-1276 
-1277 /**
-1278  * Called in response to an interaction model operation that
-1279  * responds to an event that starts zooming.
-1280  *
-1281  * It's used in the default callback for "mousedown" operations.
-1282  * Custom interaction model builders can use it to provide the default
-1283  * zooming behavior.
-1284  *
-1285  * @param { Event } event the event object which led to the startZoom call.
-1286  * @param { Dygraph} g The dygraph on which to act.
-1287  * @param { Object} context The dragging context object (with
-1288  * dragStartX/dragStartY/etc. properties). This function modifies the context.
-1289  */
-1290 Dygraph.startZoom = function(event, g, context) {
-1291   context.isZooming = true;
-1292 };
-1293 
-1294 /**
-1295  * Called in response to an interaction model operation that
-1296  * responds to an event that defines zoom boundaries.
-1297  *
-1298  * It's used in the default callback for "mousemove" operations.
-1299  * Custom interaction model builders can use it to provide the default
-1300  * zooming behavior.
-1301  *
-1302  * @param { Event } event the event object which led to the moveZoom call.
-1303  * @param { Dygraph} g The dygraph on which to act.
-1304  * @param { Object} context The dragging context object (with
-1305  * dragStartX/dragStartY/etc. properties). This function modifies the context.
-1306  */
-1307 Dygraph.moveZoom = function(event, g, context) {
-1308   context.dragEndX = g.dragGetX_(event, context);
-1309   context.dragEndY = g.dragGetY_(event, context);
-1310 
-1311   var xDelta = Math.abs(context.dragStartX - context.dragEndX);
-1312   var yDelta = Math.abs(context.dragStartY - context.dragEndY);
-1313 
-1314   // drag direction threshold for y axis is twice as large as x axis
-1315   context.dragDirection = (xDelta < yDelta / 2) ? Dygraph.VERTICAL : Dygraph.HORIZONTAL;
-1316 
-1317   g.drawZoomRect_(
-1318       context.dragDirection,
-1319       context.dragStartX,
-1320       context.dragEndX,
-1321       context.dragStartY,
-1322       context.dragEndY,
-1323       context.prevDragDirection,
-1324       context.prevEndX,
-1325       context.prevEndY);
-1326 
-1327   context.prevEndX = context.dragEndX;
-1328   context.prevEndY = context.dragEndY;
-1329   context.prevDragDirection = context.dragDirection;
-1330 };
-1331 
-1332 /**
-1333  * Called in response to an interaction model operation that
-1334  * responds to an event that performs a zoom based on previously defined
-1335  * bounds..
-1336  *
-1337  * It's used in the default callback for "mouseup" operations.
-1338  * Custom interaction model builders can use it to provide the default
-1339  * zooming behavior.
-1340  *
-1341  * @param { Event } event the event object which led to the endZoom call.
-1342  * @param { Dygraph} g The dygraph on which to end the zoom.
-1343  * @param { Object} context The dragging context object (with
-1344  * dragStartX/dragStartY/etc. properties). This function modifies the context.
-1345  */
-1346 Dygraph.endZoom = function(event, g, context) {
-1347   // TODO(konigsberg): Refactor or rename this fn -- it deals with clicks, too.
-1348   context.isZooming = false;
-1349   context.dragEndX = g.dragGetX_(event, context);
-1350   context.dragEndY = g.dragGetY_(event, context);
-1351   var regionWidth = Math.abs(context.dragEndX - context.dragStartX);
-1352   var regionHeight = Math.abs(context.dragEndY - context.dragStartY);
-1353 
-1354   if (regionWidth < 2 && regionHeight < 2 &&
-1355       g.lastx_ != undefined && g.lastx_ != -1) {
-1356     // TODO(danvk): pass along more info about the points, e.g. 'x'
-1357     if (g.attr_('clickCallback') != null) {
-1358       g.attr_('clickCallback')(event, g.lastx_, g.selPoints_);
-1359     }
-1360     if (g.attr_('pointClickCallback')) {
-1361       // check if the click was on a particular point.
-1362       var closestIdx = -1;
-1363       var closestDistance = 0;
-1364       for (var i = 0; i < g.selPoints_.length; i++) {
-1365         var p = g.selPoints_[i];
-1366         var distance = Math.pow(p.canvasx - context.dragEndX, 2) +
-1367                        Math.pow(p.canvasy - context.dragEndY, 2);
-1368         if (closestIdx == -1 || distance < closestDistance) {
-1369           closestDistance = distance;
-1370           closestIdx = i;
-1371         }
-1372       }
-1373 
-1374       // Allow any click within two pixels of the dot.
-1375       var radius = g.attr_('highlightCircleSize') + 2;
-1376       if (closestDistance <= 5 * 5) {
-1377         g.attr_('pointClickCallback')(event, g.selPoints_[closestIdx]);
-1378       }
-1379     }
-1380   }
-1381 
-1382   if (regionWidth >= 10 && context.dragDirection == Dygraph.HORIZONTAL) {
-1383     g.doZoomX_(Math.min(context.dragStartX, context.dragEndX),
-1384                Math.max(context.dragStartX, context.dragEndX));
-1385   } else if (regionHeight >= 10 && context.dragDirection == Dygraph.VERTICAL) {
-1386     g.doZoomY_(Math.min(context.dragStartY, context.dragEndY),
-1387                Math.max(context.dragStartY, context.dragEndY));
-1388   } else {
-1389     g.canvas_ctx_.clearRect(0, 0, g.canvas_.width, g.canvas_.height);
-1390   }
-1391   context.dragStartX = null;
-1392   context.dragStartY = null;
-1393 };
-1394 
-1395 /**
-1396  * Default interation model for dygraphs. You can refer to specific elements of
-1397  * this when constructing your own interaction model, e.g.:
-1398  * g.updateOptions( {
-1399  *   interactionModel: {
-1400  *     mousedown: Dygraph.defaultInteractionModel.mousedown
-1401  *   }
-1402  * } );
-1403  */
-1404 Dygraph.defaultInteractionModel = {
-1405   // Track the beginning of drag events
-1406   mousedown: function(event, g, context) {
-1407     context.initializeMouseDown(event, g, context);
-1408 
-1409     if (event.altKey || event.shiftKey) {
-1410       Dygraph.startPan(event, g, context);
-1411     } else {
-1412       Dygraph.startZoom(event, g, context);
-1413     }
-1414   },
-1415 
-1416   // Draw zoom rectangles when the mouse is down and the user moves around
-1417   mousemove: function(event, g, context) {
-1418     if (context.isZooming) {
-1419       Dygraph.moveZoom(event, g, context);
-1420     } else if (context.isPanning) {
-1421       Dygraph.movePan(event, g, context);
-1422     }
-1423   },
-1424 
-1425   mouseup: function(event, g, context) {
-1426     if (context.isZooming) {
-1427       Dygraph.endZoom(event, g, context);
-1428     } else if (context.isPanning) {
-1429       Dygraph.endPan(event, g, context);
-1430     }
-1431   },
-1432 
-1433   // Temporarily cancel the dragging event when the mouse leaves the graph
-1434   mouseout: function(event, g, context) {
-1435     if (context.isZooming) {
-1436       context.dragEndX = null;
-1437       context.dragEndY = null;
-1438     }
-1439   },
-1440 
-1441   // Disable zooming out if panning.
-1442   dblclick: function(event, g, context) {
-1443     if (event.altKey || event.shiftKey) {
-1444       return;
-1445     }
-1446     // TODO(konigsberg): replace g.doUnzoom()_ with something that is
-1447     // friendlier to public use.
-1448     g.doUnzoom_();
-1449   }
-1450 };
-1451 
-1452 Dygraph.DEFAULT_ATTRS.interactionModel = Dygraph.defaultInteractionModel;
-1453 
-1454 /**
-1455  * Set up all the mouse handlers needed to capture dragging behavior for zoom
-1456  * events.
-1457  * @private
-1458  */
-1459 Dygraph.prototype.createDragInterface_ = function() {
-1460   var context = {
-1461     // Tracks whether the mouse is down right now
-1462     isZooming: false,
-1463     isPanning: false,  // is this drag part of a pan?
-1464     is2DPan: false,    // if so, is that pan 1- or 2-dimensional?
-1465     dragStartX: null,
-1466     dragStartY: null,
-1467     dragEndX: null,
-1468     dragEndY: null,
-1469     dragDirection: null,
-1470     prevEndX: null,
-1471     prevEndY: null,
-1472     prevDragDirection: null,
-1473 
-1474     // The value on the left side of the graph when a pan operation starts.
-1475     initialLeftmostDate: null,
-1476 
-1477     // The number of units each pixel spans. (This won't be valid for log
-1478     // scales)
-1479     xUnitsPerPixel: null,
-1480 
-1481     // TODO(danvk): update this comment
-1482     // The range in second/value units that the viewport encompasses during a
-1483     // panning operation.
-1484     dateRange: null,
-1485 
-1486     // Utility function to convert page-wide coordinates to canvas coords
-1487     px: 0,
-1488     py: 0,
-1489 
-1490     // Values for use with panEdgeFraction, which limit how far outside the
-1491     // graph's data boundaries it can be panned.
-1492     boundedDates: null, // [minDate, maxDate]
-1493     boundedValues: null, // [[minValue, maxValue] ...]
-1494 
-1495     initializeMouseDown: function(event, g, context) {
-1496       // prevents mouse drags from selecting page text.
-1497       if (event.preventDefault) {
-1498         event.preventDefault();  // Firefox, Chrome, etc.
-1499       } else {
-1500         event.returnValue = false;  // IE
-1501         event.cancelBubble = true;
-1502       }
-1503 
-1504       context.px = Dygraph.findPosX(g.canvas_);
-1505       context.py = Dygraph.findPosY(g.canvas_);
-1506       context.dragStartX = g.dragGetX_(event, context);
-1507       context.dragStartY = g.dragGetY_(event, context);
-1508     }
-1509   };
-1510 
-1511   var interactionModel = this.attr_("interactionModel");
-1512 
-1513   // Self is the graph.
-1514   var self = this;
-1515 
-1516   // Function that binds the graph and context to the handler.
-1517   var bindHandler = function(handler) {
-1518     return function(event) {
-1519       handler(event, self, context);
-1520     };
-1521   };
-1522 
-1523   for (var eventName in interactionModel) {
-1524     if (!interactionModel.hasOwnProperty(eventName)) continue;
-1525     Dygraph.addEvent(this.mouseEventElement_, eventName,
-1526         bindHandler(interactionModel[eventName]));
-1527   }
-1528 
-1529   // If the user releases the mouse button during a drag, but not over the
-1530   // canvas, then it doesn't count as a zooming action.
-1531   Dygraph.addEvent(document, 'mouseup', function(event) {
-1532     if (context.isZooming || context.isPanning) {
-1533       context.isZooming = false;
-1534       context.dragStartX = null;
-1535       context.dragStartY = null;
-1536     }
-1537 
-1538     if (context.isPanning) {
-1539       context.isPanning = false;
-1540       context.draggingDate = null;
-1541       context.dateRange = null;
-1542       for (var i = 0; i < self.axes_.length; i++) {
-1543         delete self.axes_[i].draggingValue;
-1544         delete self.axes_[i].dragValueRange;
-1545       }
-1546     }
-1547   });
-1548 };
-1549 
-1550 
-1551 /**
-1552  * Draw a gray zoom rectangle over the desired area of the canvas. Also clears
-1553  * up any previous zoom rectangles that were drawn. This could be optimized to
-1554  * avoid extra redrawing, but it's tricky to avoid interactions with the status
-1555  * dots.
-1556  * 
-1557  * @param {Number} direction the direction of the zoom rectangle. Acceptable
-1558  * values are Dygraph.HORIZONTAL and Dygraph.VERTICAL.
-1559  * @param {Number} startX The X position where the drag started, in canvas
-1560  * coordinates.
-1561  * @param {Number} endX The current X position of the drag, in canvas coords.
-1562  * @param {Number} startY The Y position where the drag started, in canvas
-1563  * coordinates.
-1564  * @param {Number} endY The current Y position of the drag, in canvas coords.
-1565  * @param {Number} prevDirection the value of direction on the previous call to
-1566  * this function. Used to avoid excess redrawing
-1567  * @param {Number} prevEndX The value of endX on the previous call to this
-1568  * function. Used to avoid excess redrawing
-1569  * @param {Number} prevEndY The value of endY on the previous call to this
-1570  * function. Used to avoid excess redrawing
-1571  * @private
-1572  */
-1573 Dygraph.prototype.drawZoomRect_ = function(direction, startX, endX, startY,
-1574                                            endY, prevDirection, prevEndX,
-1575                                            prevEndY) {
-1576   var ctx = this.canvas_ctx_;
-1577 
-1578   // Clean up from the previous rect if necessary
-1579   if (prevDirection == Dygraph.HORIZONTAL) {
-1580     ctx.clearRect(Math.min(startX, prevEndX), 0,
-1581                   Math.abs(startX - prevEndX), this.height_);
-1582   } else if (prevDirection == Dygraph.VERTICAL){
-1583     ctx.clearRect(0, Math.min(startY, prevEndY),
-1584                   this.width_, Math.abs(startY - prevEndY));
-1585   }
-1586 
-1587   // Draw a light-grey rectangle to show the new viewing area
-1588   if (direction == Dygraph.HORIZONTAL) {
-1589     if (endX && startX) {
-1590       ctx.fillStyle = "rgba(128,128,128,0.33)";
-1591       ctx.fillRect(Math.min(startX, endX), 0,
-1592                    Math.abs(endX - startX), this.height_);
-1593     }
-1594   }
-1595   if (direction == Dygraph.VERTICAL) {
-1596     if (endY && startY) {
-1597       ctx.fillStyle = "rgba(128,128,128,0.33)";
-1598       ctx.fillRect(0, Math.min(startY, endY),
-1599                    this.width_, Math.abs(endY - startY));
-1600     }
-1601   }
-1602 };
-1603 
-1604 /**
-1605  * Zoom to something containing [lowX, highX]. These are pixel coordinates in
-1606  * the canvas. The exact zoom window may be slightly larger if there are no data
-1607  * points near lowX or highX. Don't confuse this function with doZoomXDates,
-1608  * which accepts dates that match the raw data. This function redraws the graph.
-1609  *
-1610  * @param {Number} lowX The leftmost pixel value that should be visible.
-1611  * @param {Number} highX The rightmost pixel value that should be visible.
-1612  * @private
-1613  */
-1614 Dygraph.prototype.doZoomX_ = function(lowX, highX) {
-1615   // Find the earliest and latest dates contained in this canvasx range.
-1616   // Convert the call to date ranges of the raw data.
-1617   var minDate = this.toDataXCoord(lowX);
-1618   var maxDate = this.toDataXCoord(highX);
-1619   this.doZoomXDates_(minDate, maxDate);
-1620 };
-1621 
-1622 /**
-1623  * Zoom to something containing [minDate, maxDate] values. Don't confuse this
-1624  * method with doZoomX which accepts pixel coordinates. This function redraws
-1625  * the graph.
-1626  *
-1627  * @param {Number} minDate The minimum date that should be visible.
-1628  * @param {Number} maxDate The maximum date that should be visible.
-1629  * @private
-1630  */
-1631 Dygraph.prototype.doZoomXDates_ = function(minDate, maxDate) {
-1632   this.dateWindow_ = [minDate, maxDate];
-1633   this.zoomed_x_ = true;
-1634   this.drawGraph_();
-1635   if (this.attr_("zoomCallback")) {
-1636     this.attr_("zoomCallback")(minDate, maxDate, this.yAxisRanges());
-1637   }
-1638 };
-1639 
-1640 /**
-1641  * Zoom to something containing [lowY, highY]. These are pixel coordinates in
-1642  * the canvas. This function redraws the graph.
-1643  *
-1644  * @param {Number} lowY The topmost pixel value that should be visible.
-1645  * @param {Number} highY The lowest pixel value that should be visible.
-1646  * @private
-1647  */
-1648 Dygraph.prototype.doZoomY_ = function(lowY, highY) {
-1649   // Find the highest and lowest values in pixel range for each axis.
-1650   // Note that lowY (in pixels) corresponds to the max Value (in data coords).
-1651   // This is because pixels increase as you go down on the screen, whereas data
-1652   // coordinates increase as you go up the screen.
-1653   var valueRanges = [];
-1654   for (var i = 0; i < this.axes_.length; i++) {
-1655     var hi = this.toDataYCoord(lowY, i);
-1656     var low = this.toDataYCoord(highY, i);
-1657     this.axes_[i].valueWindow = [low, hi];
-1658     valueRanges.push([low, hi]);
-1659   }
-1660 
-1661   this.zoomed_y_ = true;
-1662   this.drawGraph_();
-1663   if (this.attr_("zoomCallback")) {
-1664     var xRange = this.xAxisRange();
-1665     var yRange = this.yAxisRange();
-1666     this.attr_("zoomCallback")(xRange[0], xRange[1], this.yAxisRanges());
-1667   }
-1668 };
-1669 
-1670 /**
-1671  * Reset the zoom to the original view coordinates. This is the same as
-1672  * double-clicking on the graph.
-1673  *
-1674  * @private
-1675  */
-1676 Dygraph.prototype.doUnzoom_ = function() {
-1677   var dirty = false;
-1678   if (this.dateWindow_ != null) {
-1679     dirty = true;
-1680     this.dateWindow_ = null;
-1681   }
-1682 
-1683   for (var i = 0; i < this.axes_.length; i++) {
-1684     if (this.axes_[i].valueWindow != null) {
-1685       dirty = true;
-1686       delete this.axes_[i].valueWindow;
-1687     }
-1688   }
-1689 
-1690   if (dirty) {
-1691     // Putting the drawing operation before the callback because it resets
-1692     // yAxisRange.
-1693     this.zoomed_x_ = false;
-1694     this.zoomed_y_ = false;
-1695     this.drawGraph_();
-1696     if (this.attr_("zoomCallback")) {
-1697       var minDate = this.rawData_[0][0];
-1698       var maxDate = this.rawData_[this.rawData_.length - 1][0];
-1699       this.attr_("zoomCallback")(minDate, maxDate, this.yAxisRanges());
-1700     }
-1701   }
-1702 };
-1703 
-1704 /**
-1705  * When the mouse moves in the canvas, display information about a nearby data
-1706  * point and draw dots over those points in the data series. This function
-1707  * takes care of cleanup of previously-drawn dots.
-1708  * @param {Object} event The mousemove event from the browser.
-1709  * @private
-1710  */
-1711 Dygraph.prototype.mouseMove_ = function(event) {
-1712   // This prevents JS errors when mousing over the canvas before data loads.
-1713   var points = this.layout_.points;
-1714   if (points === undefined) return;
-1715 
-1716   var canvasx = Dygraph.pageX(event) - Dygraph.findPosX(this.mouseEventElement_);
-1717 
-1718   var lastx = -1;
-1719   var lasty = -1;
-1720 
-1721   // Loop through all the points and find the date nearest to our current
-1722   // location.
-1723   var minDist = 1e+100;
-1724   var idx = -1;
-1725   for (var i = 0; i < points.length; i++) {
-1726     var point = points[i];
-1727     if (point == null) continue;
-1728     var dist = Math.abs(point.canvasx - canvasx);
-1729     if (dist > minDist) continue;
-1730     minDist = dist;
-1731     idx = i;
-1732   }
-1733   if (idx >= 0) lastx = points[idx].xval;
-1734 
-1735   // Extract the points we've selected
-1736   this.selPoints_ = [];
-1737   var l = points.length;
-1738   if (!this.attr_("stackedGraph")) {
-1739     for (var i = 0; i < l; i++) {
-1740       if (points[i].xval == lastx) {
-1741         this.selPoints_.push(points[i]);
-1742       }
-1743     }
-1744   } else {
-1745     // Need to 'unstack' points starting from the bottom
-1746     var cumulative_sum = 0;
-1747     for (var i = l - 1; i >= 0; i--) {
-1748       if (points[i].xval == lastx) {
-1749         var p = {};  // Clone the point since we modify it
-1750         for (var k in points[i]) {
-1751           p[k] = points[i][k];
-1752         }
-1753         p.yval -= cumulative_sum;
-1754         cumulative_sum += p.yval;
-1755         this.selPoints_.push(p);
-1756       }
-1757     }
-1758     this.selPoints_.reverse();
-1759   }
-1760 
-1761   if (this.attr_("highlightCallback")) {
-1762     var px = this.lastx_;
-1763     if (px !== null && lastx != px) {
-1764       // only fire if the selected point has changed.
-1765       this.attr_("highlightCallback")(event, lastx, this.selPoints_, this.idxToRow_(idx));
-1766     }
-1767   }
-1768 
-1769   // Save last x position for callbacks.
-1770   this.lastx_ = lastx;
-1771 
-1772   this.updateSelection_();
-1773 };
-1774 
-1775 /**
-1776  * Transforms layout_.points index into data row number.
-1777  * @param int layout_.points index
-1778  * @return int row number, or -1 if none could be found.
-1779  * @private
-1780  */
-1781 Dygraph.prototype.idxToRow_ = function(idx) {
-1782   if (idx < 0) return -1;
-1783 
-1784   for (var i in this.layout_.datasets) {
-1785     if (idx < this.layout_.datasets[i].length) {
-1786       return this.boundaryIds_[0][0]+idx;
-1787     }
-1788     idx -= this.layout_.datasets[i].length;
-1789   }
-1790   return -1;
-1791 };
-1792 
-1793 /**
-1794  * @private
-1795  * @param { Number } x The number to consider.
-1796  * @return { Boolean } Whether the number is zero or NaN.
-1797  */
-1798 // TODO(danvk): rename this function to something like 'isNonZeroNan'.
-1799 Dygraph.isOK = function(x) {
-1800   return x && !isNaN(x);
-1801 };
-1802 
-1803 /**
-1804  * @private
-1805  * Generates HTML for the legend which is displayed when hovering over the
-1806  * chart. If no selected points are specified, a default legend is returned
-1807  * (this may just be the empty string).
-1808  * @param { Number } [x] The x-value of the selected points.
-1809  * @param { [Object] } [sel_points] List of selected points for the given
-1810  * x-value. Should have properties like 'name', 'yval' and 'canvasy'.
-1811  */
-1812 Dygraph.prototype.generateLegendHTML_ = function(x, sel_points) {
-1813   // If no points are selected, we display a default legend. Traditionally,
-1814   // this has been blank. But a better default would be a conventional legend,
-1815   // which provides essential information for a non-interactive chart.
-1816   if (typeof(x) === 'undefined') {
-1817     if (this.attr_('legend') != 'always') return '';
-1818 
-1819     var sepLines = this.attr_('labelsSeparateLines');
-1820     var labels = this.attr_('labels');
-1821     var html = '';
-1822     for (var i = 1; i < labels.length; i++) {
-1823       if (!this.visibility()[i - 1]) continue;
-1824       var c = this.plotter_.colors[labels[i]];
-1825       if (html != '') html += (sepLines ? '<br/>' : ' ');
-1826       html += "<b><span style='color: " + c + ";'>—" + labels[i] +
-1827         "</span></b>";
-1828     }
-1829     return html;
-1830   }
-1831 
-1832   var html = this.attr_('xValueFormatter')(x) + ":";
-1833 
-1834   var fmtFunc = this.attr_('yValueFormatter');
-1835   var showZeros = this.attr_("labelsShowZeroValues");
-1836   var sepLines = this.attr_("labelsSeparateLines");
-1837   for (var i = 0; i < this.selPoints_.length; i++) {
-1838     var pt = this.selPoints_[i];
-1839     if (pt.yval == 0 && !showZeros) continue;
-1840     if (!Dygraph.isOK(pt.canvasy)) continue;
-1841     if (sepLines) html += "<br/>";
-1842 
-1843     var c = this.plotter_.colors[pt.name];
-1844     var yval = fmtFunc(pt.yval, this);
-1845     // TODO(danvk): use a template string here and make it an attribute.
-1846     html += " <b><span style='color: " + c + ";'>"
-1847       + pt.name + "</span></b>:"
-1848       + yval;
-1849   }
-1850   return html;
-1851 };
-1852 
-1853 /**
-1854  * @private
-1855  * Displays information about the selected points in the legend. If there is no
-1856  * selection, the legend will be cleared.
-1857  * @param { Number } [x] The x-value of the selected points.
-1858  * @param { [Object] } [sel_points] List of selected points for the given
-1859  * x-value. Should have properties like 'name', 'yval' and 'canvasy'.
-1860  */
-1861 Dygraph.prototype.setLegendHTML_ = function(x, sel_points) {
-1862   var html = this.generateLegendHTML_(x, sel_points);
-1863   var labelsDiv = this.attr_("labelsDiv");
-1864   if (labelsDiv !== null) {
-1865     labelsDiv.innerHTML = html;
-1866   } else {
-1867     if (typeof(this.shown_legend_error_) == 'undefined') {
-1868       this.error('labelsDiv is set to something nonexistent; legend will not be shown.');
-1869       this.shown_legend_error_ = true;
-1870     }
-1871   }
-1872 };
-1873 
-1874 /**
-1875  * Draw dots over the selectied points in the data series. This function
-1876  * takes care of cleanup of previously-drawn dots.
-1877  * @private
-1878  */
-1879 Dygraph.prototype.updateSelection_ = function() {
-1880   // Clear the previously drawn vertical, if there is one
-1881   var ctx = this.canvas_ctx_;
-1882   if (this.previousVerticalX_ >= 0) {
-1883     // Determine the maximum highlight circle size.
-1884     var maxCircleSize = 0;
-1885     var labels = this.attr_('labels');
-1886     for (var i = 1; i < labels.length; i++) {
-1887       var r = this.attr_('highlightCircleSize', labels[i]);
-1888       if (r > maxCircleSize) maxCircleSize = r;
-1889     }
-1890     var px = this.previousVerticalX_;
-1891     ctx.clearRect(px - maxCircleSize - 1, 0,
-1892                   2 * maxCircleSize + 2, this.height_);
-1893   }
-1894 
-1895   if (this.selPoints_.length > 0) {
-1896     // Set the status message to indicate the selected point(s)
-1897     if (this.attr_('showLabelsOnHighlight')) {
-1898       this.setLegendHTML_(this.lastx_, this.selPoints_);
-1899     }
-1900 
-1901     // Draw colored circles over the center of each selected point
-1902     var canvasx = this.selPoints_[0].canvasx;
-1903     ctx.save();
-1904     for (var i = 0; i < this.selPoints_.length; i++) {
-1905       var pt = this.selPoints_[i];
-1906       if (!Dygraph.isOK(pt.canvasy)) continue;
-1907 
-1908       var circleSize = this.attr_('highlightCircleSize', pt.name);
-1909       ctx.beginPath();
-1910       ctx.fillStyle = this.plotter_.colors[pt.name];
-1911       ctx.arc(canvasx, pt.canvasy, circleSize, 0, 2 * Math.PI, false);
-1912       ctx.fill();
-1913     }
-1914     ctx.restore();
-1915 
-1916     this.previousVerticalX_ = canvasx;
-1917   }
-1918 };
-1919 
-1920 /**
-1921  * Manually set the selected points and display information about them in the
-1922  * legend. The selection can be cleared using clearSelection() and queried
-1923  * using getSelection().
-1924  * @param { Integer } row number that should be highlighted (i.e. appear with
-1925  * hover dots on the chart). Set to false to clear any selection.
-1926  */
-1927 Dygraph.prototype.setSelection = function(row) {
-1928   // Extract the points we've selected
-1929   this.selPoints_ = [];
-1930   var pos = 0;
-1931 
-1932   if (row !== false) {
-1933     row = row-this.boundaryIds_[0][0];
-1934   }
-1935 
-1936   if (row !== false && row >= 0) {
-1937     for (var i in this.layout_.datasets) {
-1938       if (row < this.layout_.datasets[i].length) {
-1939         var point = this.layout_.points[pos+row];
-1940         
-1941         if (this.attr_("stackedGraph")) {
-1942           point = this.layout_.unstackPointAtIndex(pos+row);
-1943         }
-1944         
-1945         this.selPoints_.push(point);
-1946       }
-1947       pos += this.layout_.datasets[i].length;
-1948     }
-1949   }
-1950 
-1951   if (this.selPoints_.length) {
-1952     this.lastx_ = this.selPoints_[0].xval;
-1953     this.updateSelection_();
-1954   } else {
-1955     this.clearSelection();
-1956   }
-1957 
-1958 };
-1959 
-1960 /**
-1961  * The mouse has left the canvas. Clear out whatever artifacts remain
-1962  * @param {Object} event the mouseout event from the browser.
-1963  * @private
-1964  */
-1965 Dygraph.prototype.mouseOut_ = function(event) {
-1966   if (this.attr_("unhighlightCallback")) {
-1967     this.attr_("unhighlightCallback")(event);
-1968   }
-1969 
-1970   if (this.attr_("hideOverlayOnMouseOut")) {
-1971     this.clearSelection();
-1972   }
-1973 };
-1974 
-1975 /**
-1976  * Clears the current selection (i.e. points that were highlighted by moving
-1977  * the mouse over the chart).
-1978  */
-1979 Dygraph.prototype.clearSelection = function() {
-1980   // Get rid of the overlay data
-1981   this.canvas_ctx_.clearRect(0, 0, this.width_, this.height_);
-1982   this.setLegendHTML_();
-1983   this.selPoints_ = [];
-1984   this.lastx_ = -1;
-1985 }
-1986 
-1987 /**
-1988  * Returns the number of the currently selected row. To get data for this row,
-1989  * you can use the getValue method.
-1990  * @return { Integer } row number, or -1 if nothing is selected
-1991  */
-1992 Dygraph.prototype.getSelection = function() {
-1993   if (!this.selPoints_ || this.selPoints_.length < 1) {
-1994     return -1;
-1995   }
-1996 
-1997   for (var row=0; row<this.layout_.points.length; row++ ) {
-1998     if (this.layout_.points[row].x == this.selPoints_[0].x) {
-1999       return row + this.boundaryIds_[0][0];
-2000     }
-2001   }
-2002   return -1;
-2003 };
-2004 
-2005 /**
-2006  * Number formatting function which mimicks the behavior of %g in printf, i.e.
-2007  * either exponential or fixed format (without trailing 0s) is used depending on
-2008  * the length of the generated string.  The advantage of this format is that
-2009  * there is a predictable upper bound on the resulting string length,
-2010  * significant figures are not dropped, and normal numbers are not displayed in
-2011  * exponential notation.
-2012  *
-2013  * NOTE: JavaScript's native toPrecision() is NOT a drop-in replacement for %g.
-2014  * It creates strings which are too long for absolute values between 10^-4 and
-2015  * 10^-6, e.g. '0.00001' instead of '1e-5'. See tests/number-format.html for
-2016  * output examples.
-2017  *
-2018  * @param {Number} x The number to format
-2019  * @param {Number} opt_precision The precision to use, default 2.
-2020  * @return {String} A string formatted like %g in printf.  The max generated
-2021  *                  string length should be precision + 6 (e.g 1.123e+300).
-2022  */
-2023 Dygraph.floatFormat = function(x, opt_precision) {
-2024   // Avoid invalid precision values; [1, 21] is the valid range.
-2025   var p = Math.min(Math.max(1, opt_precision || 2), 21);
-2026 
-2027   // This is deceptively simple.  The actual algorithm comes from:
-2028   //
-2029   // Max allowed length = p + 4
-2030   // where 4 comes from 'e+n' and '.'.
-2031   //
-2032   // Length of fixed format = 2 + y + p
-2033   // where 2 comes from '0.' and y = # of leading zeroes.
-2034   //
-2035   // Equating the two and solving for y yields y = 2, or 0.00xxxx which is
-2036   // 1.0e-3.
-2037   //
-2038   // Since the behavior of toPrecision() is identical for larger numbers, we
-2039   // don't have to worry about the other bound.
-2040   //
-2041   // Finally, the argument for toExponential() is the number of trailing digits,
-2042   // so we take off 1 for the value before the '.'.
-2043   return (Math.abs(x) < 1.0e-3 && x != 0.0) ?
-2044       x.toExponential(p - 1) : x.toPrecision(p);
-2045 };
-2046 
-2047 /**
-2048  * @private
-2049  * Return a string version of a number. This respects the digitsAfterDecimal
-2050  * and maxNumberWidth options.
-2051  * @param {Number} x The number to be formatted
-2052  * @param {Dygraph} g The dygraph object
-2053  */
-2054 Dygraph.numberFormatter = function(x, g) {
-2055   var sigFigs = g.attr_('sigFigs');
-2056 
-2057   if (sigFigs !== null) {
-2058     // User has opted for a fixed number of significant figures.
-2059     return Dygraph.floatFormat(x, sigFigs);
-2060   }
-2061 
-2062   var digits = g.attr_('digitsAfterDecimal');
-2063   var maxNumberWidth = g.attr_('maxNumberWidth');
-2064 
-2065   // switch to scientific notation if we underflow or overflow fixed display.
-2066   if (x !== 0.0 &&
-2067       (Math.abs(x) >= Math.pow(10, maxNumberWidth) ||
-2068        Math.abs(x) < Math.pow(10, -digits))) {
-2069     return x.toExponential(digits);
-2070   } else {
-2071     return '' + Dygraph.round_(x, digits);
-2072   }
-2073 };
-2074 
-2075 /**
-2076  * @private
-2077  * Converts '9' to '09' (useful for dates)
-2078  */
-2079 Dygraph.zeropad = function(x) {
-2080   if (x < 10) return "0" + x; else return "" + x;
-2081 };
-2082 
-2083 /**
-2084  * Return a string version of the hours, minutes and seconds portion of a date.
-2085  * @param {Number} date The JavaScript date (ms since epoch)
-2086  * @return {String} A time of the form "HH:MM:SS"
-2087  * @private
-2088  */
-2089 Dygraph.hmsString_ = function(date) {
-2090   var zeropad = Dygraph.zeropad;
-2091   var d = new Date(date);
-2092   if (d.getSeconds()) {
-2093     return zeropad(d.getHours()) + ":" +
-2094            zeropad(d.getMinutes()) + ":" +
-2095            zeropad(d.getSeconds());
-2096   } else {
-2097     return zeropad(d.getHours()) + ":" + zeropad(d.getMinutes());
-2098   }
-2099 };
-2100 
-2101 /**
-2102  * Convert a JS date to a string appropriate to display on an axis that
-2103  * is displaying values at the stated granularity.
-2104  * @param {Date} date The date to format
-2105  * @param {Number} granularity One of the Dygraph granularity constants
-2106  * @return {String} The formatted date
-2107  * @private
-2108  */
-2109 Dygraph.dateAxisFormatter = function(date, granularity) {
-2110   if (granularity >= Dygraph.DECADAL) {
-2111     return date.strftime('%Y');
-2112   } else if (granularity >= Dygraph.MONTHLY) {
-2113     return date.strftime('%b %y');
-2114   } else {
-2115     var frac = date.getHours() * 3600 + date.getMinutes() * 60 + date.getSeconds() + date.getMilliseconds();
-2116     if (frac == 0 || granularity >= Dygraph.DAILY) {
-2117       return new Date(date.getTime() + 3600*1000).strftime('%d%b');
-2118     } else {
-2119       return Dygraph.hmsString_(date.getTime());
-2120     }
-2121   }
-2122 };
-2123 
-2124 /**
-2125  * Convert a JS date (millis since epoch) to YYYY/MM/DD
-2126  * @param {Number} date The JavaScript date (ms since epoch)
-2127  * @return {String} A date of the form "YYYY/MM/DD"
-2128  * @private
-2129  */
-2130 Dygraph.dateString_ = function(date) {
-2131   var zeropad = Dygraph.zeropad;
-2132   var d = new Date(date);
-2133 
-2134   // Get the year:
-2135   var year = "" + d.getFullYear();
-2136   // Get a 0 padded month string
-2137   var month = zeropad(d.getMonth() + 1);  //months are 0-offset, sigh
-2138   // Get a 0 padded day string
-2139   var day = zeropad(d.getDate());
-2140 
-2141   var ret = "";
-2142   var frac = d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds();
-2143   if (frac) ret = " " + Dygraph.hmsString_(date);
-2144 
-2145   return year + "/" + month + "/" + day + ret;
-2146 };
-2147 
-2148 /**
-2149  * Round a number to the specified number of digits past the decimal point.
-2150  * @param {Number} num The number to round
-2151  * @param {Number} places The number of decimals to which to round
-2152  * @return {Number} The rounded number
-2153  * @private
-2154  */
-2155 Dygraph.round_ = function(num, places) {
-2156   var shift = Math.pow(10, places);
-2157   return Math.round(num * shift)/shift;
-2158 };
-2159 
-2160 /**
-2161  * Fires when there's data available to be graphed.
-2162  * @param {String} data Raw CSV data to be plotted
-2163  * @private
-2164  */
-2165 Dygraph.prototype.loadedEvent_ = function(data) {
-2166   this.rawData_ = this.parseCSV_(data);
-2167   this.predraw_();
-2168 };
-2169 
-2170 Dygraph.prototype.months =  ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
-2171                              "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
-2172 Dygraph.prototype.quarters = ["Jan", "Apr", "Jul", "Oct"];
-2173 
-2174 /**
-2175  * Add ticks on the x-axis representing years, months, quarters, weeks, or days
-2176  * @private
-2177  */
-2178 Dygraph.prototype.addXTicks_ = function() {
-2179   // Determine the correct ticks scale on the x-axis: quarterly, monthly, ...
-2180   var range;
-2181   if (this.dateWindow_) {
-2182     range = [this.dateWindow_[0], this.dateWindow_[1]];
-2183   } else {
-2184     range = [this.rawData_[0][0], this.rawData_[this.rawData_.length - 1][0]];
-2185   }
-2186 
-2187   var xTicks = this.attr_('xTicker')(range[0], range[1], this);
-2188   this.layout_.updateOptions({xTicks: xTicks});
-2189 };
-2190 
-2191 // Time granularity enumeration
-2192 Dygraph.SECONDLY = 0;
-2193 Dygraph.TWO_SECONDLY = 1;
-2194 Dygraph.FIVE_SECONDLY = 2;
-2195 Dygraph.TEN_SECONDLY = 3;
-2196 Dygraph.THIRTY_SECONDLY  = 4;
-2197 Dygraph.MINUTELY = 5;
-2198 Dygraph.TWO_MINUTELY = 6;
-2199 Dygraph.FIVE_MINUTELY = 7;
-2200 Dygraph.TEN_MINUTELY = 8;
-2201 Dygraph.THIRTY_MINUTELY = 9;
-2202 Dygraph.HOURLY = 10;
-2203 Dygraph.TWO_HOURLY = 11;
-2204 Dygraph.SIX_HOURLY = 12;
-2205 Dygraph.DAILY = 13;
-2206 Dygraph.WEEKLY = 14;
-2207 Dygraph.MONTHLY = 15;
-2208 Dygraph.QUARTERLY = 16;
-2209 Dygraph.BIANNUAL = 17;
-2210 Dygraph.ANNUAL = 18;
-2211 Dygraph.DECADAL = 19;
-2212 Dygraph.CENTENNIAL = 20;
-2213 Dygraph.NUM_GRANULARITIES = 21;
-2214 
-2215 Dygraph.SHORT_SPACINGS = [];
-2216 Dygraph.SHORT_SPACINGS[Dygraph.SECONDLY]        = 1000 * 1;
-2217 Dygraph.SHORT_SPACINGS[Dygraph.TWO_SECONDLY]    = 1000 * 2;
-2218 Dygraph.SHORT_SPACINGS[Dygraph.FIVE_SECONDLY]   = 1000 * 5;
-2219 Dygraph.SHORT_SPACINGS[Dygraph.TEN_SECONDLY]    = 1000 * 10;
-2220 Dygraph.SHORT_SPACINGS[Dygraph.THIRTY_SECONDLY] = 1000 * 30;
-2221 Dygraph.SHORT_SPACINGS[Dygraph.MINUTELY]        = 1000 * 60;
-2222 Dygraph.SHORT_SPACINGS[Dygraph.TWO_MINUTELY]    = 1000 * 60 * 2;
-2223 Dygraph.SHORT_SPACINGS[Dygraph.FIVE_MINUTELY]   = 1000 * 60 * 5;
-2224 Dygraph.SHORT_SPACINGS[Dygraph.TEN_MINUTELY]    = 1000 * 60 * 10;
-2225 Dygraph.SHORT_SPACINGS[Dygraph.THIRTY_MINUTELY] = 1000 * 60 * 30;
-2226 Dygraph.SHORT_SPACINGS[Dygraph.HOURLY]          = 1000 * 3600;
-2227 Dygraph.SHORT_SPACINGS[Dygraph.TWO_HOURLY]      = 1000 * 3600 * 2;
-2228 Dygraph.SHORT_SPACINGS[Dygraph.SIX_HOURLY]      = 1000 * 3600 * 6;
-2229 Dygraph.SHORT_SPACINGS[Dygraph.DAILY]           = 1000 * 86400;
-2230 Dygraph.SHORT_SPACINGS[Dygraph.WEEKLY]          = 1000 * 604800;
-2231 
-2232 /**
-2233  * @private
-2234  * If we used this time granularity, how many ticks would there be?
-2235  * This is only an approximation, but it's generally good enough.
-2236  */
-2237 Dygraph.prototype.NumXTicks = function(start_time, end_time, granularity) {
-2238   if (granularity < Dygraph.MONTHLY) {
-2239     // Generate one tick mark for every fixed interval of time.
-2240     var spacing = Dygraph.SHORT_SPACINGS[granularity];
-2241     return Math.floor(0.5 + 1.0 * (end_time - start_time) / spacing);
-2242   } else {
-2243     var year_mod = 1;  // e.g. to only print one point every 10 years.
-2244     var num_months = 12;
-2245     if (granularity == Dygraph.QUARTERLY) num_months = 3;
-2246     if (granularity == Dygraph.BIANNUAL) num_months = 2;
-2247     if (granularity == Dygraph.ANNUAL) num_months = 1;
-2248     if (granularity == Dygraph.DECADAL) { num_months = 1; year_mod = 10; }
-2249     if (granularity == Dygraph.CENTENNIAL) { num_months = 1; year_mod = 100; }
-2250 
-2251     var msInYear = 365.2524 * 24 * 3600 * 1000;
-2252     var num_years = 1.0 * (end_time - start_time) / msInYear;
-2253     return Math.floor(0.5 + 1.0 * num_years * num_months / year_mod);
-2254   }
-2255 };
-2256 
-2257 /**
-2258  * @private
-2259  *
-2260  * Construct an x-axis of nicely-formatted times on meaningful boundaries
-2261  * (e.g. 'Jan 09' rather than 'Jan 22, 2009').
-2262  *
-2263  * Returns an array containing {v: millis, label: label} dictionaries.
-2264  */
-2265 Dygraph.prototype.GetXAxis = function(start_time, end_time, granularity) {
-2266   var formatter = this.attr_("xAxisLabelFormatter");
-2267   var ticks = [];
-2268   if (granularity < Dygraph.MONTHLY) {
-2269     // Generate one tick mark for every fixed interval of time.
-2270     var spacing = Dygraph.SHORT_SPACINGS[granularity];
-2271     var format = '%d%b';  // e.g. "1Jan"
-2272 
-2273     // Find a time less than start_time which occurs on a "nice" time boundary
-2274     // for this granularity.
-2275     var g = spacing / 1000;
-2276     var d = new Date(start_time);
-2277     if (g <= 60) {  // seconds
-2278       var x = d.getSeconds(); d.setSeconds(x - x % g);
-2279     } else {
-2280       d.setSeconds(0);
-2281       g /= 60;
-2282       if (g <= 60) {  // minutes
-2283         var x = d.getMinutes(); d.setMinutes(x - x % g);
-2284       } else {
-2285         d.setMinutes(0);
-2286         g /= 60;
-2287 
-2288         if (g <= 24) {  // days
-2289           var x = d.getHours(); d.setHours(x - x % g);
-2290         } else {
-2291           d.setHours(0);
-2292           g /= 24;
-2293 
-2294           if (g == 7) {  // one week
-2295             d.setDate(d.getDate() - d.getDay());
-2296           }
-2297         }
-2298       }
-2299     }
-2300     start_time = d.getTime();
-2301 
-2302     for (var t = start_time; t <= end_time; t += spacing) {
-2303       ticks.push({ v:t, label: formatter(new Date(t), granularity) });
-2304     }
-2305   } else {
-2306     // Display a tick mark on the first of a set of months of each year.
-2307     // Years get a tick mark iff y % year_mod == 0. This is useful for
-2308     // displaying a tick mark once every 10 years, say, on long time scales.
-2309     var months;
-2310     var year_mod = 1;  // e.g. to only print one point every 10 years.
-2311 
-2312     if (granularity == Dygraph.MONTHLY) {
-2313       months = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ];
-2314     } else if (granularity == Dygraph.QUARTERLY) {
-2315       months = [ 0, 3, 6, 9 ];
-2316     } else if (granularity == Dygraph.BIANNUAL) {
-2317       months = [ 0, 6 ];
-2318     } else if (granularity == Dygraph.ANNUAL) {
-2319       months = [ 0 ];
-2320     } else if (granularity == Dygraph.DECADAL) {
-2321       months = [ 0 ];
-2322       year_mod = 10;
-2323     } else if (granularity == Dygraph.CENTENNIAL) {
-2324       months = [ 0 ];
-2325       year_mod = 100;
-2326     } else {
-2327       this.warn("Span of dates is too long");
-2328     }
-2329 
-2330     var start_year = new Date(start_time).getFullYear();
-2331     var end_year   = new Date(end_time).getFullYear();
-2332     var zeropad = Dygraph.zeropad;
-2333     for (var i = start_year; i <= end_year; i++) {
-2334       if (i % year_mod != 0) continue;
-2335       for (var j = 0; j < months.length; j++) {
-2336         var date_str = i + "/" + zeropad(1 + months[j]) + "/01";
-2337         var t = Dygraph.dateStrToMillis(date_str);
-2338         if (t < start_time || t > end_time) continue;
-2339         ticks.push({ v:t, label: formatter(new Date(t), granularity) });
-2340       }
-2341     }
-2342   }
-2343 
-2344   return ticks;
-2345 };
-2346 
-2347 
-2348 /**
-2349  * Add ticks to the x-axis based on a date range.
-2350  * @param {Number} startDate Start of the date window (millis since epoch)
-2351  * @param {Number} endDate End of the date window (millis since epoch)
-2352  * @param {Dygraph} self The dygraph object
-2353  * @return { [Object] } Array of {label, value} tuples.
-2354  * @public
-2355  */
-2356 Dygraph.dateTicker = function(startDate, endDate, self) {
-2357   // TODO(danvk): why does this take 'self' as a param?
-2358   var chosen = -1;
-2359   for (var i = 0; i < Dygraph.NUM_GRANULARITIES; i++) {
-2360     var num_ticks = self.NumXTicks(startDate, endDate, i);
-2361     if (self.width_ / num_ticks >= self.attr_('pixelsPerXLabel')) {
-2362       chosen = i;
-2363       break;
-2364     }
-2365   }
-2366 
-2367   if (chosen >= 0) {
-2368     return self.GetXAxis(startDate, endDate, chosen);
-2369   } else {
-2370     // TODO(danvk): signal error.
-2371   }
-2372 };
-2373 
-2374 /**
-2375  * @private
-2376  * This is a list of human-friendly values at which to show tick marks on a log
-2377  * scale. It is k * 10^n, where k=1..9 and n=-39..+39, so:
-2378  * ..., 1, 2, 3, 4, 5, ..., 9, 10, 20, 30, ..., 90, 100, 200, 300, ...
-2379  * NOTE: this assumes that Dygraph.LOG_SCALE = 10.
-2380  */
-2381 Dygraph.PREFERRED_LOG_TICK_VALUES = function() {
-2382   var vals = [];
-2383   for (var power = -39; power <= 39; power++) {
-2384     var range = Math.pow(10, power);
-2385     for (var mult = 1; mult <= 9; mult++) {
-2386       var val = range * mult;
-2387       vals.push(val);
-2388     }
-2389   }
-2390   return vals;
-2391 }();
-2392 
-2393 /**
-2394  * @private
-2395  * Implementation of binary search over an array.
-2396  * Currently does not work when val is outside the range of arry's values.
-2397  * @param { Integer } val the value to search for
-2398  * @param { Integer[] } arry is the value over which to search
-2399  * @param { Integer } abs If abs > 0, find the lowest entry greater than val
-2400  * If abs < 0, find the highest entry less than val.
-2401  * if abs == 0, find the entry that equals val.
-2402  * @param { Integer } [low] The first index in arry to consider (optional)
-2403  * @param { Integer } [high] The last index in arry to consider (optional)
-2404  */
-2405 Dygraph.binarySearch = function(val, arry, abs, low, high) {
-2406   if (low == null || high == null) {
-2407     low = 0;
-2408     high = arry.length - 1;
-2409   }
-2410   if (low > high) {
-2411     return -1;
-2412   }
-2413   if (abs == null) {
-2414     abs = 0;
-2415   }
-2416   var validIndex = function(idx) {
-2417     return idx >= 0 && idx < arry.length;
-2418   }
-2419   var mid = parseInt((low + high) / 2);
-2420   var element = arry[mid];
-2421   if (element == val) {
-2422     return mid;
-2423   }
-2424   if (element > val) {
-2425     if (abs > 0) {
-2426       // Accept if element > val, but also if prior element < val.
-2427       var idx = mid - 1;
-2428       if (validIndex(idx) && arry[idx] < val) {
-2429         return mid;
-2430       }
-2431     }
-2432     return Dygraph.binarySearch(val, arry, abs, low, mid - 1);
-2433   }
-2434   if (element < val) {
-2435     if (abs < 0) {
-2436       // Accept if element < val, but also if prior element > val.
-2437       var idx = mid + 1;
-2438       if (validIndex(idx) && arry[idx] > val) {
-2439         return mid;
-2440       }
-2441     }
-2442     return Dygraph.binarySearch(val, arry, abs, mid + 1, high);
-2443   }
-2444 };
-2445 
-2446 // TODO(konigsberg): Update comment.
-2447 /**
-2448  * Add ticks when the x axis has numbers on it (instead of dates)
-2449  *
-2450  * @param {Number} minV minimum value
-2451  * @param {Number} maxV maximum value
-2452  * @param self
-2453  * @param {function} attribute accessor function.
-2454  * @return {[Object]} Array of {label, value} tuples.
-2455  */
-2456 Dygraph.numericTicks = function(minV, maxV, self, axis_props, vals) {
-2457   var attr = function(k) {
-2458     if (axis_props && axis_props.hasOwnProperty(k)) return axis_props[k];
-2459     return self.attr_(k);
-2460   };
-2461 
-2462   var ticks = [];
-2463   if (vals) {
-2464     for (var i = 0; i < vals.length; i++) {
-2465       ticks.push({v: vals[i]});
-2466     }
-2467   } else {
-2468     if (axis_props && attr("logscale")) {
-2469       var pixelsPerTick = attr('pixelsPerYLabel');
-2470       // NOTE(konigsberg): Dan, should self.height_ be self.plotter_.area.h?
-2471       var nTicks  = Math.floor(self.height_ / pixelsPerTick);
-2472       var minIdx = Dygraph.binarySearch(minV, Dygraph.PREFERRED_LOG_TICK_VALUES, 1);
-2473       var maxIdx = Dygraph.binarySearch(maxV, Dygraph.PREFERRED_LOG_TICK_VALUES, -1);
-2474       if (minIdx == -1) {
-2475         minIdx = 0;
-2476       }
-2477       if (maxIdx == -1) {
-2478         maxIdx = Dygraph.PREFERRED_LOG_TICK_VALUES.length - 1;
-2479       }
-2480       // Count the number of tick values would appear, if we can get at least
-2481       // nTicks / 4 accept them.
-2482       var lastDisplayed = null;
-2483       if (maxIdx - minIdx >= nTicks / 4) {
-2484         var axisId = axis_props.yAxisId;
-2485         for (var idx = maxIdx; idx >= minIdx; idx--) {
-2486           var tickValue = Dygraph.PREFERRED_LOG_TICK_VALUES[idx];
-2487           var domCoord = axis_props.g.toDomYCoord(tickValue, axisId);
-2488           var tick = { v: tickValue };
-2489           if (lastDisplayed == null) {
-2490             lastDisplayed = {
-2491               tickValue : tickValue,
-2492               domCoord : domCoord
-2493             };
-2494           } else {
-2495             if (domCoord - lastDisplayed.domCoord >= pixelsPerTick) {
-2496               lastDisplayed = {
-2497                 tickValue : tickValue,
-2498                 domCoord : domCoord
-2499               };
-2500             } else {
-2501               tick.label = "";
-2502             }
-2503           }
-2504           ticks.push(tick);
-2505         }
-2506         // Since we went in backwards order.
-2507         ticks.reverse();
-2508       }
-2509     }
-2510 
-2511     // ticks.length won't be 0 if the log scale function finds values to insert.
-2512     if (ticks.length == 0) {
-2513       // Basic idea:
-2514       // Try labels every 1, 2, 5, 10, 20, 50, 100, etc.
-2515       // Calculate the resulting tick spacing (i.e. this.height_ / nTicks).
-2516       // The first spacing greater than pixelsPerYLabel is what we use.
-2517       // TODO(danvk): version that works on a log scale.
-2518       if (attr("labelsKMG2")) {
-2519         var mults = [1, 2, 4, 8];
-2520       } else {
-2521         var mults = [1, 2, 5];
-2522       }
-2523       var scale, low_val, high_val, nTicks;
-2524       // TODO(danvk): make it possible to set this for x- and y-axes independently.
-2525       var pixelsPerTick = attr('pixelsPerYLabel');
-2526       for (var i = -10; i < 50; i++) {
-2527         if (attr("labelsKMG2")) {
-2528           var base_scale = Math.pow(16, i);
-2529         } else {
-2530           var base_scale = Math.pow(10, i);
-2531         }
-2532         for (var j = 0; j < mults.length; j++) {
-2533           scale = base_scale * mults[j];
-2534           low_val = Math.floor(minV / scale) * scale;
-2535           high_val = Math.ceil(maxV / scale) * scale;
-2536           nTicks = Math.abs(high_val - low_val) / scale;
-2537           var spacing = self.height_ / nTicks;
-2538           // wish I could break out of both loops at once...
-2539           if (spacing > pixelsPerTick) break;
-2540         }
-2541         if (spacing > pixelsPerTick) break;
-2542       }
-2543 
-2544       // Construct the set of ticks.
-2545       // Allow reverse y-axis if it's explicitly requested.
-2546       if (low_val > high_val) scale *= -1;
-2547       for (var i = 0; i < nTicks; i++) {
-2548         var tickV = low_val + i * scale;
-2549         ticks.push( {v: tickV} );
-2550       }
-2551     }
-2552   }
-2553 
-2554   // Add formatted labels to the ticks.
-2555   var k;
-2556   var k_labels = [];
-2557   if (attr("labelsKMB")) {
-2558     k = 1000;
-2559     k_labels = [ "K", "M", "B", "T" ];
-2560   }
-2561   if (attr("labelsKMG2")) {
-2562     if (k) self.warn("Setting both labelsKMB and labelsKMG2. Pick one!");
-2563     k = 1024;
-2564     k_labels = [ "k", "M", "G", "T" ];
-2565   }
-2566   var formatter = attr('yAxisLabelFormatter') ?
-2567       attr('yAxisLabelFormatter') : attr('yValueFormatter');
-2568 
-2569   // Add labels to the ticks.
-2570   for (var i = 0; i < ticks.length; i++) {
-2571     if (ticks[i].label !== undefined) continue;  // Use current label.
-2572     var tickV = ticks[i].v;
-2573     var absTickV = Math.abs(tickV);
-2574     var label = formatter(tickV, self);
-2575     if (k_labels.length > 0) {
-2576       // Round up to an appropriate unit.
-2577       var n = k*k*k*k;
-2578       for (var j = 3; j >= 0; j--, n /= k) {
-2579         if (absTickV >= n) {
-2580           label = Dygraph.round_(tickV / n, attr('digitsAfterDecimal')) + k_labels[j];
-2581           break;
-2582         }
-2583       }
-2584     }
-2585     ticks[i].label = label;
-2586   }
-2587 
-2588   return ticks;
-2589 };
-2590 
-2591 /**
-2592  * @private
-2593  * Computes the range of the data series (including confidence intervals).
-2594  * @param { [Array] } series either [ [x1, y1], [x2, y2], ... ] or
-2595  * [ [x1, [y1, dev_low, dev_high]], [x2, [y2, dev_low, dev_high]], ...
-2596  * @return [low, high]
-2597  */
-2598 Dygraph.prototype.extremeValues_ = function(series) {
-2599   var minY = null, maxY = null;
-2600 
-2601   var bars = this.attr_("errorBars") || this.attr_("customBars");
-2602   if (bars) {
-2603     // With custom bars, maxY is the max of the high values.
-2604     for (var j = 0; j < series.length; j++) {
-2605       var y = series[j][1][0];
-2606       if (!y) continue;
-2607       var low = y - series[j][1][1];
-2608       var high = y + series[j][1][2];
-2609       if (low > y) low = y;    // this can happen with custom bars,
-2610       if (high < y) high = y;  // e.g. in tests/custom-bars.html
-2611       if (maxY == null || high > maxY) {
-2612         maxY = high;
-2613       }
-2614       if (minY == null || low < minY) {
-2615         minY = low;
-2616       }
-2617     }
-2618   } else {
-2619     for (var j = 0; j < series.length; j++) {
-2620       var y = series[j][1];
-2621       if (y === null || isNaN(y)) continue;
-2622       if (maxY == null || y > maxY) {
-2623         maxY = y;
-2624       }
-2625       if (minY == null || y < minY) {
-2626         minY = y;
-2627       }
-2628     }
-2629   }
-2630 
-2631   return [minY, maxY];
-2632 };
-2633 
-2634 /**
-2635  * @private
-2636  * This function is called once when the chart's data is changed or the options
-2637  * dictionary is updated. It is _not_ called when the user pans or zooms. The
-2638  * idea is that values derived from the chart's data can be computed here,
-2639  * rather than every time the chart is drawn. This includes things like the
-2640  * number of axes, rolling averages, etc.
-2641  */
-2642 Dygraph.prototype.predraw_ = function() {
-2643   // TODO(danvk): move more computations out of drawGraph_ and into here.
-2644   this.computeYAxes_();
-2645 
-2646   // Create a new plotter.
-2647   if (this.plotter_) this.plotter_.clear();
-2648   this.plotter_ = new DygraphCanvasRenderer(this,
-2649                                             this.hidden_,
-2650                                             this.hidden_ctx_,
-2651                                             this.layout_,
-2652                                             this.renderOptions_);
-2653 
-2654   // The roller sits in the bottom left corner of the chart. We don't know where
-2655   // this will be until the options are available, so it's positioned here.
-2656   this.createRollInterface_();
-2657 
-2658   // Same thing applies for the labelsDiv. It's right edge should be flush with
-2659   // the right edge of the charting area (which may not be the same as the right
-2660   // edge of the div, if we have two y-axes.
-2661   this.positionLabelsDiv_();
-2662 
-2663   // If the data or options have changed, then we'd better redraw.
-2664   this.drawGraph_();
-2665 };
-2666 
-2667 /**
-2668  * Update the graph with new data. This method is called when the viewing area
-2669  * has changed. If the underlying data or options have changed, predraw_ will
-2670  * be called before drawGraph_ is called.
-2671  * @private
-2672  */
-2673 Dygraph.prototype.drawGraph_ = function() {
-2674   var data = this.rawData_;
-2675 
-2676   // This is used to set the second parameter to drawCallback, below.
-2677   var is_initial_draw = this.is_initial_draw_;
-2678   this.is_initial_draw_ = false;
-2679 
-2680   var minY = null, maxY = null;
-2681   this.layout_.removeAllDatasets();
-2682   this.setColors_();
-2683   this.attrs_['pointSize'] = 0.5 * this.attr_('highlightCircleSize');
-2684 
-2685   // Loop over the fields (series).  Go from the last to the first,
-2686   // because if they're stacked that's how we accumulate the values.
-2687 
-2688   var cumulative_y = [];  // For stacked series.
-2689   var datasets = [];
-2690 
-2691   var extremes = {};  // series name -> [low, high]
-2692 
-2693   // Loop over all fields and create datasets
-2694   for (var i = data[0].length - 1; i >= 1; i--) {
-2695     if (!this.visibility()[i - 1]) continue;
-2696 
-2697     var seriesName = this.attr_("labels")[i];
-2698     var connectSeparatedPoints = this.attr_('connectSeparatedPoints', i);
-2699     var logScale = this.attr_('logscale', i);
-2700 
-2701     var series = [];
-2702     for (var j = 0; j < data.length; j++) {
-2703       var date = data[j][0];
-2704       var point = data[j][i];
-2705       if (logScale) {
-2706         // On the log scale, points less than zero do not exist.
-2707         // This will create a gap in the chart. Note that this ignores
-2708         // connectSeparatedPoints.
-2709         if (point <= 0) {
-2710           point = null;
-2711         }
-2712         series.push([date, point]);
-2713       } else {
-2714         if (point != null || !connectSeparatedPoints) {
-2715           series.push([date, point]);
-2716         }
-2717       }
-2718     }
-2719 
-2720     // TODO(danvk): move this into predraw_. It's insane to do it here.
-2721     series = this.rollingAverage(series, this.rollPeriod_);
-2722 
-2723     // Prune down to the desired range, if necessary (for zooming)
-2724     // Because there can be lines going to points outside of the visible area,
-2725     // we actually prune to visible points, plus one on either side.
-2726     var bars = this.attr_("errorBars") || this.attr_("customBars");
-2727     if (this.dateWindow_) {
-2728       var low = this.dateWindow_[0];
-2729       var high= this.dateWindow_[1];
-2730       var pruned = [];
-2731       // TODO(danvk): do binary search instead of linear search.
-2732       // TODO(danvk): pass firstIdx and lastIdx directly to the renderer.
-2733       var firstIdx = null, lastIdx = null;
-2734       for (var k = 0; k < series.length; k++) {
-2735         if (series[k][0] >= low && firstIdx === null) {
-2736           firstIdx = k;
-2737         }
-2738         if (series[k][0] <= high) {
-2739           lastIdx = k;
-2740         }
-2741       }
-2742       if (firstIdx === null) firstIdx = 0;
-2743       if (firstIdx > 0) firstIdx--;
-2744       if (lastIdx === null) lastIdx = series.length - 1;
-2745       if (lastIdx < series.length - 1) lastIdx++;
-2746       this.boundaryIds_[i-1] = [firstIdx, lastIdx];
-2747       for (var k = firstIdx; k <= lastIdx; k++) {
-2748         pruned.push(series[k]);
-2749       }
-2750       series = pruned;
-2751     } else {
-2752       this.boundaryIds_[i-1] = [0, series.length-1];
-2753     }
-2754 
-2755     var seriesExtremes = this.extremeValues_(series);
-2756 
-2757     if (bars) {
-2758       for (var j=0; j<series.length; j++) {
-2759         val = [series[j][0], series[j][1][0], series[j][1][1], series[j][1][2]];
-2760         series[j] = val;
-2761       }
-2762     } else if (this.attr_("stackedGraph")) {
-2763       var l = series.length;
-2764       var actual_y;
-2765       for (var j = 0; j < l; j++) {
-2766         // If one data set has a NaN, let all subsequent stacked
-2767         // sets inherit the NaN -- only start at 0 for the first set.
-2768         var x = series[j][0];
-2769         if (cumulative_y[x] === undefined) {
-2770           cumulative_y[x] = 0;
-2771         }
-2772 
-2773         actual_y = series[j][1];
-2774         cumulative_y[x] += actual_y;
-2775 
-2776         series[j] = [x, cumulative_y[x]]
-2777 
-2778         if (cumulative_y[x] > seriesExtremes[1]) {
-2779           seriesExtremes[1] = cumulative_y[x];
-2780         }
-2781         if (cumulative_y[x] < seriesExtremes[0]) {
-2782           seriesExtremes[0] = cumulative_y[x];
-2783         }
-2784       }
-2785     }
-2786     extremes[seriesName] = seriesExtremes;
-2787 
-2788     datasets[i] = series;
-2789   }
-2790 
-2791   for (var i = 1; i < datasets.length; i++) {
-2792     if (!this.visibility()[i - 1]) continue;
-2793     this.layout_.addDataset(this.attr_("labels")[i], datasets[i]);
-2794   }
-2795 
-2796   this.computeYAxisRanges_(extremes);
-2797   this.layout_.updateOptions( { yAxes: this.axes_,
-2798                                 seriesToAxisMap: this.seriesToAxisMap_
-2799                               } );
-2800   this.addXTicks_();
-2801 
-2802   // Save the X axis zoomed status as the updateOptions call will tend to set it errorneously
-2803   var tmp_zoomed_x = this.zoomed_x_;
-2804   // Tell PlotKit to use this new data and render itself
-2805   this.layout_.updateOptions({dateWindow: this.dateWindow_});
-2806   this.zoomed_x_ = tmp_zoomed_x;
-2807   this.layout_.evaluateWithError();
-2808   this.plotter_.clear();
-2809   this.plotter_.render();
-2810   this.canvas_.getContext('2d').clearRect(0, 0, this.canvas_.width,
-2811                                           this.canvas_.height);
-2812 
-2813   if (is_initial_draw) {
-2814     // Generate a static legend before any particular point is selected.
-2815     this.setLegendHTML_();
-2816   } else {
-2817     if (typeof(this.selPoints_) !== 'undefined' && this.selPoints_.length) {
-2818       this.lastx_ = this.selPoints_[0].xval;
-2819       this.updateSelection_();
-2820     } else {
-2821       this.clearSelection();
-2822     }
-2823   }
-2824 
-2825   if (this.attr_("drawCallback") !== null) {
-2826     this.attr_("drawCallback")(this, is_initial_draw);
-2827   }
-2828 };
-2829 
-2830 /**
-2831  * @private
-2832  * Determine properties of the y-axes which are independent of the data
-2833  * currently being displayed. This includes things like the number of axes and
-2834  * the style of the axes. It does not include the range of each axis and its
-2835  * tick marks.
-2836  * This fills in this.axes_ and this.seriesToAxisMap_.
-2837  * axes_ = [ { options } ]
-2838  * seriesToAxisMap_ = { seriesName: 0, seriesName2: 1, ... }
-2839  *   indices are into the axes_ array.
-2840  */
-2841 Dygraph.prototype.computeYAxes_ = function() {
-2842   this.axes_ = [{ yAxisId : 0, g : this }];  // always have at least one y-axis.
-2843   this.seriesToAxisMap_ = {};
-2844 
-2845   // Get a list of series names.
-2846   var labels = this.attr_("labels");
-2847   var series = {};
-2848   for (var i = 1; i < labels.length; i++) series[labels[i]] = (i - 1);
-2849 
-2850   // all options which could be applied per-axis:
-2851   var axisOptions = [
-2852     'includeZero',
-2853     'valueRange',
-2854     'labelsKMB',
-2855     'labelsKMG2',
-2856     'pixelsPerYLabel',
-2857     'yAxisLabelWidth',
-2858     'axisLabelFontSize',
-2859     'axisTickSize',
-2860     'logscale'
-2861   ];
-2862 
-2863   // Copy global axis options over to the first axis.
-2864   for (var i = 0; i < axisOptions.length; i++) {
-2865     var k = axisOptions[i];
-2866     var v = this.attr_(k);
-2867     if (v) this.axes_[0][k] = v;
-2868   }
-2869 
-2870   // Go through once and add all the axes.
-2871   for (var seriesName in series) {
-2872     if (!series.hasOwnProperty(seriesName)) continue;
-2873     var axis = this.attr_("axis", seriesName);
-2874     if (axis == null) {
-2875       this.seriesToAxisMap_[seriesName] = 0;
-2876       continue;
-2877     }
-2878     if (typeof(axis) == 'object') {
-2879       // Add a new axis, making a copy of its per-axis options.
-2880       var opts = {};
-2881       Dygraph.update(opts, this.axes_[0]);
-2882       Dygraph.update(opts, { valueRange: null });  // shouldn't inherit this.
-2883       var yAxisId = this.axes_.length;
-2884       opts.yAxisId = yAxisId;
-2885       opts.g = this;
-2886       Dygraph.update(opts, axis);
-2887       this.axes_.push(opts);
-2888       this.seriesToAxisMap_[seriesName] = yAxisId;
-2889     }
-2890   }
-2891 
-2892   // Go through one more time and assign series to an axis defined by another
-2893   // series, e.g. { 'Y1: { axis: {} }, 'Y2': { axis: 'Y1' } }
-2894   for (var seriesName in series) {
-2895     if (!series.hasOwnProperty(seriesName)) continue;
-2896     var axis = this.attr_("axis", seriesName);
-2897     if (typeof(axis) == 'string') {
-2898       if (!this.seriesToAxisMap_.hasOwnProperty(axis)) {
-2899         this.error("Series " + seriesName + " wants to share a y-axis with " +
-2900                    "series " + axis + ", which does not define its own axis.");
-2901         return null;
-2902       }
-2903       var idx = this.seriesToAxisMap_[axis];
-2904       this.seriesToAxisMap_[seriesName] = idx;
-2905     }
-2906   }
-2907 
-2908   // Now we remove series from seriesToAxisMap_ which are not visible. We do
-2909   // this last so that hiding the first series doesn't destroy the axis
-2910   // properties of the primary axis.
-2911   var seriesToAxisFiltered = {};
-2912   var vis = this.visibility();
-2913   for (var i = 1; i < labels.length; i++) {
-2914     var s = labels[i];
-2915     if (vis[i - 1]) seriesToAxisFiltered[s] = this.seriesToAxisMap_[s];
-2916   }
-2917   this.seriesToAxisMap_ = seriesToAxisFiltered;
-2918 };
-2919 
-2920 /**
-2921  * Returns the number of y-axes on the chart.
-2922  * @return {Number} the number of axes.
-2923  */
-2924 Dygraph.prototype.numAxes = function() {
-2925   var last_axis = 0;
-2926   for (var series in this.seriesToAxisMap_) {
-2927     if (!this.seriesToAxisMap_.hasOwnProperty(series)) continue;
-2928     var idx = this.seriesToAxisMap_[series];
-2929     if (idx > last_axis) last_axis = idx;
-2930   }
-2931   return 1 + last_axis;
-2932 };
-2933 
-2934 /**
-2935  * @private
-2936  * Determine the value range and tick marks for each axis.
-2937  * @param {Object} extremes A mapping from seriesName -> [low, high]
-2938  * This fills in the valueRange and ticks fields in each entry of this.axes_.
-2939  */
-2940 Dygraph.prototype.computeYAxisRanges_ = function(extremes) {
-2941   // Build a map from axis number -> [list of series names]
-2942   var seriesForAxis = [];
-2943   for (var series in this.seriesToAxisMap_) {
-2944     if (!this.seriesToAxisMap_.hasOwnProperty(series)) continue;
-2945     var idx = this.seriesToAxisMap_[series];
-2946     while (seriesForAxis.length <= idx) seriesForAxis.push([]);
-2947     seriesForAxis[idx].push(series);
-2948   }
-2949 
-2950   // Compute extreme values, a span and tick marks for each axis.
-2951   for (var i = 0; i < this.axes_.length; i++) {
-2952     var axis = this.axes_[i];
-2953  
-2954     if (!seriesForAxis[i]) {
-2955       // If no series are defined or visible then use a reasonable default
-2956       axis.extremeRange = [0, 1];
-2957     } else {
-2958       // Calculate the extremes of extremes.
-2959       var series = seriesForAxis[i];
-2960       var minY = Infinity;  // extremes[series[0]][0];
-2961       var maxY = -Infinity;  // extremes[series[0]][1];
-2962       var extremeMinY, extremeMaxY;
-2963       for (var j = 0; j < series.length; j++) {
-2964         // Only use valid extremes to stop null data series' from corrupting the scale.
-2965         extremeMinY = extremes[series[j]][0];
-2966         if (extremeMinY != null) {
-2967           minY = Math.min(extremeMinY, minY);
-2968         }
-2969         extremeMaxY = extremes[series[j]][1];
-2970         if (extremeMaxY != null) {
-2971           maxY = Math.max(extremeMaxY, maxY);
-2972         }
-2973       }
-2974       if (axis.includeZero && minY > 0) minY = 0;
-2975 
-2976       // Ensure we have a valid scale, otherwise defualt to zero for safety.
-2977       if (minY == Infinity) minY = 0;
-2978       if (maxY == -Infinity) maxY = 0;
-2979 
-2980       // Add some padding and round up to an integer to be human-friendly.
-2981       var span = maxY - minY;
-2982       // special case: if we have no sense of scale, use +/-10% of the sole value.
-2983       if (span == 0) { span = maxY; }
-2984 
-2985       var maxAxisY;
-2986       var minAxisY;
-2987       if (axis.logscale) {
-2988         var maxAxisY = maxY + 0.1 * span;
-2989         var minAxisY = minY;
-2990       } else {
-2991         var maxAxisY = maxY + 0.1 * span;
-2992         var minAxisY = minY - 0.1 * span;
-2993 
-2994         // Try to include zero and make it minAxisY (or maxAxisY) if it makes sense.
-2995         if (!this.attr_("avoidMinZero")) {
-2996           if (minAxisY < 0 && minY >= 0) minAxisY = 0;
-2997           if (maxAxisY > 0 && maxY <= 0) maxAxisY = 0;
-2998         }
-2999 
-3000         if (this.attr_("includeZero")) {
-3001           if (maxY < 0) maxAxisY = 0;
-3002           if (minY > 0) minAxisY = 0;
-3003         }
-3004       }
-3005       axis.extremeRange = [minAxisY, maxAxisY];
-3006     }
-3007     if (axis.valueWindow) {
-3008       // This is only set if the user has zoomed on the y-axis. It is never set
-3009       // by a user. It takes precedence over axis.valueRange because, if you set
-3010       // valueRange, you'd still expect to be able to pan.
-3011       axis.computedValueRange = [axis.valueWindow[0], axis.valueWindow[1]];
-3012     } else if (axis.valueRange) {
-3013       // This is a user-set value range for this axis.
-3014       axis.computedValueRange = [axis.valueRange[0], axis.valueRange[1]];
-3015     } else {
-3016       axis.computedValueRange = axis.extremeRange;
-3017     }
-3018 
-3019     // Add ticks. By default, all axes inherit the tick positions of the
-3020     // primary axis. However, if an axis is specifically marked as having
-3021     // independent ticks, then that is permissible as well.
-3022     if (i == 0 || axis.independentTicks) {
-3023       axis.ticks =
-3024         Dygraph.numericTicks(axis.computedValueRange[0],
-3025                              axis.computedValueRange[1],
-3026                              this,
-3027                              axis);
-3028     } else {
-3029       var p_axis = this.axes_[0];
-3030       var p_ticks = p_axis.ticks;
-3031       var p_scale = p_axis.computedValueRange[1] - p_axis.computedValueRange[0];
-3032       var scale = axis.computedValueRange[1] - axis.computedValueRange[0];
-3033       var tick_values = [];
-3034       for (var i = 0; i < p_ticks.length; i++) {
-3035         var y_frac = (p_ticks[i].v - p_axis.computedValueRange[0]) / p_scale;
-3036         var y_val = axis.computedValueRange[0] + y_frac * scale;
-3037         tick_values.push(y_val);
-3038       }
-3039 
-3040       axis.ticks =
-3041         Dygraph.numericTicks(axis.computedValueRange[0],
-3042                              axis.computedValueRange[1],
-3043                              this, axis, tick_values);
-3044     }
-3045   }
-3046 };
-3047  
-3048 /**
-3049  * @private
-3050  * Calculates the rolling average of a data set.
-3051  * If originalData is [label, val], rolls the average of those.
-3052  * If originalData is [label, [, it's interpreted as [value, stddev]
-3053  *   and the roll is returned in the same form, with appropriately reduced
-3054  *   stddev for each value.
-3055  * Note that this is where fractional input (i.e. '5/10') is converted into
-3056  *   decimal values.
-3057  * @param {Array} originalData The data in the appropriate format (see above)
-3058  * @param {Number} rollPeriod The number of points over which to average the
-3059  *                            data
-3060  */
-3061 Dygraph.prototype.rollingAverage = function(originalData, rollPeriod) {
-3062   if (originalData.length < 2)
-3063     return originalData;
-3064   var rollPeriod = Math.min(rollPeriod, originalData.length - 1);
-3065   var rollingData = [];
-3066   var sigma = this.attr_("sigma");
-3067 
-3068   if (this.fractions_) {
-3069     var num = 0;
-3070     var den = 0;  // numerator/denominator
-3071     var mult = 100.0;
-3072     for (var i = 0; i < originalData.length; i++) {
-3073       num += originalData[i][1][0];
-3074       den += originalData[i][1][1];
-3075       if (i - rollPeriod >= 0) {
-3076         num -= originalData[i - rollPeriod][1][0];
-3077         den -= originalData[i - rollPeriod][1][1];
-3078       }
-3079 
-3080       var date = originalData[i][0];
-3081       var value = den ? num / den : 0.0;
-3082       if (this.attr_("errorBars")) {
-3083         if (this.wilsonInterval_) {
-3084           // For more details on this confidence interval, see:
-3085           // http://en.wikipedia.org/wiki/Binomial_confidence_interval
-3086           if (den) {
-3087             var p = value < 0 ? 0 : value, n = den;
-3088             var pm = sigma * Math.sqrt(p*(1-p)/n + sigma*sigma/(4*n*n));
-3089             var denom = 1 + sigma * sigma / den;
-3090             var low  = (p + sigma * sigma / (2 * den) - pm) / denom;
-3091             var high = (p + sigma * sigma / (2 * den) + pm) / denom;
-3092             rollingData[i] = [date,
-3093                               [p * mult, (p - low) * mult, (high - p) * mult]];
-3094           } else {
-3095             rollingData[i] = [date, [0, 0, 0]];
-3096           }
-3097         } else {
-3098           var stddev = den ? sigma * Math.sqrt(value * (1 - value) / den) : 1.0;
-3099           rollingData[i] = [date, [mult * value, mult * stddev, mult * stddev]];
-3100         }
-3101       } else {
-3102         rollingData[i] = [date, mult * value];
-3103       }
-3104     }
-3105   } else if (this.attr_("customBars")) {
-3106     var low = 0;
-3107     var mid = 0;
-3108     var high = 0;
-3109     var count = 0;
-3110     for (var i = 0; i < originalData.length; i++) {
-3111       var data = originalData[i][1];
-3112       var y = data[1];
-3113       rollingData[i] = [originalData[i][0], [y, y - data[0], data[2] - y]];
-3114 
-3115       if (y != null && !isNaN(y)) {
-3116         low += data[0];
-3117         mid += y;
-3118         high += data[2];
-3119         count += 1;
-3120       }
-3121       if (i - rollPeriod >= 0) {
-3122         var prev = originalData[i - rollPeriod];
-3123         if (prev[1][1] != null && !isNaN(prev[1][1])) {
-3124           low -= prev[1][0];
-3125           mid -= prev[1][1];
-3126           high -= prev[1][2];
-3127           count -= 1;
-3128         }
-3129       }
-3130       rollingData[i] = [originalData[i][0], [ 1.0 * mid / count,
-3131                                               1.0 * (mid - low) / count,
-3132                                               1.0 * (high - mid) / count ]];
-3133     }
-3134   } else {
-3135     // Calculate the rolling average for the first rollPeriod - 1 points where
-3136     // there is not enough data to roll over the full number of points
-3137     var num_init_points = Math.min(rollPeriod - 1, originalData.length - 2);
-3138     if (!this.attr_("errorBars")){
-3139       if (rollPeriod == 1) {
-3140         return originalData;
-3141       }
-3142 
-3143       for (var i = 0; i < originalData.length; i++) {
-3144         var sum = 0;
-3145         var num_ok = 0;
-3146         for (var j = Math.max(0, i - rollPeriod + 1); j < i + 1; j++) {
-3147           var y = originalData[j][1];
-3148           if (y == null || isNaN(y)) continue;
-3149           num_ok++;
-3150           sum += originalData[j][1];
-3151         }
-3152         if (num_ok) {
-3153           rollingData[i] = [originalData[i][0], sum / num_ok];
-3154         } else {
-3155           rollingData[i] = [originalData[i][0], null];
-3156         }
-3157       }
-3158 
-3159     } else {
-3160       for (var i = 0; i < originalData.length; i++) {
-3161         var sum = 0;
-3162         var variance = 0;
-3163         var num_ok = 0;
-3164         for (var j = Math.max(0, i - rollPeriod + 1); j < i + 1; j++) {
-3165           var y = originalData[j][1][0];
-3166           if (y == null || isNaN(y)) continue;
-3167           num_ok++;
-3168           sum += originalData[j][1][0];
-3169           variance += Math.pow(originalData[j][1][1], 2);
-3170         }
-3171         if (num_ok) {
-3172           var stddev = Math.sqrt(variance) / num_ok;
-3173           rollingData[i] = [originalData[i][0],
-3174                             [sum / num_ok, sigma * stddev, sigma * stddev]];
-3175         } else {
-3176           rollingData[i] = [originalData[i][0], [null, null, null]];
-3177         }
-3178       }
-3179     }
-3180   }
-3181 
-3182   return rollingData;
-3183 };
-3184 
-3185 /**
-3186  * @private
-3187  * Parses a date, returning the number of milliseconds since epoch. This can be
-3188  * passed in as an xValueParser in the Dygraph constructor.
-3189  * TODO(danvk): enumerate formats that this understands.
-3190  * @param {String} A date in YYYYMMDD format.
-3191  * @return {Number} Milliseconds since epoch.
-3192  */
-3193 Dygraph.dateParser = function(dateStr, self) {
-3194   var dateStrSlashed;
-3195   var d;
-3196   if (dateStr.search("-") != -1) {  // e.g. '2009-7-12' or '2009-07-12'
-3197     dateStrSlashed = dateStr.replace("-", "/", "g");
-3198     while (dateStrSlashed.search("-") != -1) {
-3199       dateStrSlashed = dateStrSlashed.replace("-", "/");
-3200     }
-3201     d = Dygraph.dateStrToMillis(dateStrSlashed);
-3202   } else if (dateStr.length == 8) {  // e.g. '20090712'
-3203     // TODO(danvk): remove support for this format. It's confusing.
-3204     dateStrSlashed = dateStr.substr(0,4) + "/" + dateStr.substr(4,2)
-3205                        + "/" + dateStr.substr(6,2);
-3206     d = Dygraph.dateStrToMillis(dateStrSlashed);
-3207   } else {
-3208     // Any format that Date.parse will accept, e.g. "2009/07/12" or
-3209     // "2009/07/12 12:34:56"
-3210     d = Dygraph.dateStrToMillis(dateStr);
-3211   }
-3212 
-3213   if (!d || isNaN(d)) {
-3214     self.error("Couldn't parse " + dateStr + " as a date");
-3215   }
-3216   return d;
-3217 };
-3218 
-3219 /**
-3220  * Detects the type of the str (date or numeric) and sets the various
-3221  * formatting attributes in this.attrs_ based on this type.
-3222  * @param {String} str An x value.
-3223  * @private
-3224  */
-3225 Dygraph.prototype.detectTypeFromString_ = function(str) {
-3226   var isDate = false;
-3227   if (str.indexOf('-') > 0 ||
-3228       str.indexOf('/') >= 0 ||
-3229       isNaN(parseFloat(str))) {
-3230     isDate = true;
-3231   } else if (str.length == 8 && str > '19700101' && str < '20371231') {
-3232     // TODO(danvk): remove support for this format.
-3233     isDate = true;
-3234   }
-3235 
-3236   if (isDate) {
-3237     this.attrs_.xValueFormatter = Dygraph.dateString_;
-3238     this.attrs_.xValueParser = Dygraph.dateParser;
-3239     this.attrs_.xTicker = Dygraph.dateTicker;
-3240     this.attrs_.xAxisLabelFormatter = Dygraph.dateAxisFormatter;
-3241   } else {
-3242     // TODO(danvk): use Dygraph.numberFormatter here?
-3243     this.attrs_.xValueFormatter = function(x) { return x; };
-3244     this.attrs_.xValueParser = function(x) { return parseFloat(x); };
-3245     this.attrs_.xTicker = Dygraph.numericTicks;
-3246     this.attrs_.xAxisLabelFormatter = this.attrs_.xValueFormatter;
-3247   }
-3248 };
-3249 
-3250 /**
-3251  * Parses the value as a floating point number. This is like the parseFloat()
-3252  * built-in, but with a few differences:
-3253  * - the empty string is parsed as null, rather than NaN.
-3254  * - if the string cannot be parsed at all, an error is logged.
-3255  * If the string can't be parsed, this method returns null.
-3256  * @param {String} x The string to be parsed
-3257  * @param {Number} opt_line_no The line number from which the string comes.
-3258  * @param {String} opt_line The text of the line from which the string comes.
-3259  * @private
-3260  */
-3261 
-3262 // Parse the x as a float or return null if it's not a number.
-3263 Dygraph.prototype.parseFloat_ = function(x, opt_line_no, opt_line) {
-3264   var val = parseFloat(x);
-3265   if (!isNaN(val)) return val;
-3266 
-3267   // Try to figure out what happeend.
-3268   // If the value is the empty string, parse it as null.
-3269   if (/^ *$/.test(x)) return null;
-3270 
-3271   // If it was actually "NaN", return it as NaN.
-3272   if (/^ *nan *$/i.test(x)) return NaN;
-3273 
-3274   // Looks like a parsing error.
-3275   var msg = "Unable to parse '" + x + "' as a number";
-3276   if (opt_line !== null && opt_line_no !== null) {
-3277     msg += " on line " + (1+opt_line_no) + " ('" + opt_line + "') of CSV.";
-3278   }
-3279   this.error(msg);
-3280 
-3281   return null;
-3282 };
-3283 
-3284 /**
-3285  * @private
-3286  * Parses a string in a special csv format.  We expect a csv file where each
-3287  * line is a date point, and the first field in each line is the date string.
-3288  * We also expect that all remaining fields represent series.
-3289  * if the errorBars attribute is set, then interpret the fields as:
-3290  * date, series1, stddev1, series2, stddev2, ...
-3291  * @param {[Object]} data See above.
-3292  *
-3293  * @return [Object] An array with one entry for each row. These entries
-3294  * are an array of cells in that row. The first entry is the parsed x-value for
-3295  * the row. The second, third, etc. are the y-values. These can take on one of
-3296  * three forms, depending on the CSV and constructor parameters:
-3297  * 1. numeric value
-3298  * 2. [ value, stddev ]
-3299  * 3. [ low value, center value, high value ]
-3300  */
-3301 Dygraph.prototype.parseCSV_ = function(data) {
-3302   var ret = [];
-3303   var lines = data.split("\n");
-3304 
-3305   // Use the default delimiter or fall back to a tab if that makes sense.
-3306   var delim = this.attr_('delimiter');
-3307   if (lines[0].indexOf(delim) == -1 && lines[0].indexOf('\t') >= 0) {
-3308     delim = '\t';
-3309   }
-3310 
-3311   var start = 0;
-3312   if (this.labelsFromCSV_) {
-3313     start = 1;
-3314     this.attrs_.labels = lines[0].split(delim);
-3315   }
-3316   var line_no = 0;
-3317 
-3318   var xParser;
-3319   var defaultParserSet = false;  // attempt to auto-detect x value type
-3320   var expectedCols = this.attr_("labels").length;
-3321   var outOfOrder = false;
-3322   for (var i = start; i < lines.length; i++) {
-3323     var line = lines[i];
-3324     line_no = i;
-3325     if (line.length == 0) continue;  // skip blank lines
-3326     if (line[0] == '#') continue;    // skip comment lines
-3327     var inFields = line.split(delim);
-3328     if (inFields.length < 2) continue;
-3329 
-3330     var fields = [];
-3331     if (!defaultParserSet) {
-3332       this.detectTypeFromString_(inFields[0]);
-3333       xParser = this.attr_("xValueParser");
-3334       defaultParserSet = true;
-3335     }
-3336     fields[0] = xParser(inFields[0], this);
-3337 
-3338     // If fractions are expected, parse the numbers as "A/B"
-3339     if (this.fractions_) {
-3340       for (var j = 1; j < inFields.length; j++) {
-3341         // TODO(danvk): figure out an appropriate way to flag parse errors.
-3342         var vals = inFields[j].split("/");
-3343         if (vals.length != 2) {
-3344           this.error('Expected fractional "num/den" values in CSV data ' +
-3345                      "but found a value '" + inFields[j] + "' on line " +
-3346                      (1 + i) + " ('" + line + "') which is not of this form.");
-3347           fields[j] = [0, 0];
-3348         } else {
-3349           fields[j] = [this.parseFloat_(vals[0], i, line),
-3350                        this.parseFloat_(vals[1], i, line)];
-3351         }
-3352       }
-3353     } else if (this.attr_("errorBars")) {
-3354       // If there are error bars, values are (value, stddev) pairs
-3355       if (inFields.length % 2 != 1) {
-3356         this.error('Expected alternating (value, stdev.) pairs in CSV data ' +
-3357                    'but line ' + (1 + i) + ' has an odd number of values (' +
-3358                    (inFields.length - 1) + "): '" + line + "'");
-3359       }
-3360       for (var j = 1; j < inFields.length; j += 2) {
-3361         fields[(j + 1) / 2] = [this.parseFloat_(inFields[j], i, line),
-3362                                this.parseFloat_(inFields[j + 1], i, line)];
-3363       }
-3364     } else if (this.attr_("customBars")) {
-3365       // Bars are a low;center;high tuple
-3366       for (var j = 1; j < inFields.length; j++) {
-3367         var val = inFields[j];
-3368         if (/^ *$/.test(val)) {
-3369           fields[j] = [null, null, null];
-3370         } else {
-3371           var vals = val.split(";");
-3372           if (vals.length == 3) {
-3373             fields[j] = [ this.parseFloat_(vals[0], i, line),
-3374                           this.parseFloat_(vals[1], i, line),
-3375                           this.parseFloat_(vals[2], i, line) ];
-3376           } else {
-3377             this.warning('When using customBars, values must be either blank ' +
-3378                          'or "low;center;high" tuples (got "' + val +
-3379                          '" on line ' + (1+i));
-3380           }
-3381         }
-3382       }
-3383     } else {
-3384       // Values are just numbers
-3385       for (var j = 1; j < inFields.length; j++) {
-3386         fields[j] = this.parseFloat_(inFields[j], i, line);
-3387       }
-3388     }
-3389     if (ret.length > 0 && fields[0] < ret[ret.length - 1][0]) {
-3390       outOfOrder = true;
-3391     }
-3392 
-3393     if (fields.length != expectedCols) {
-3394       this.error("Number of columns in line " + i + " (" + fields.length +
-3395                  ") does not agree with number of labels (" + expectedCols +
-3396                  ") " + line);
-3397     }
-3398 
-3399     // If the user specified the 'labels' option and none of the cells of the
-3400     // first row parsed correctly, then they probably double-specified the
-3401     // labels. We go with the values set in the option, discard this row and
-3402     // log a warning to the JS console.
-3403     if (i == 0 && this.attr_('labels')) {
-3404       var all_null = true;
-3405       for (var j = 0; all_null && j < fields.length; j++) {
-3406         if (fields[j]) all_null = false;
-3407       }
-3408       if (all_null) {
-3409         this.warn("The dygraphs 'labels' option is set, but the first row of " +
-3410                   "CSV data ('" + line + "') appears to also contain labels. " +
-3411                   "Will drop the CSV labels and use the option labels.");
-3412         continue;
-3413       }
-3414     }
-3415     ret.push(fields);
-3416   }
-3417 
-3418   if (outOfOrder) {
-3419     this.warn("CSV is out of order; order it correctly to speed loading.");
-3420     ret.sort(function(a,b) { return a[0] - b[0] });
-3421   }
-3422 
-3423   return ret;
-3424 };
-3425 
-3426 /**
-3427  * @private
-3428  * The user has provided their data as a pre-packaged JS array. If the x values
-3429  * are numeric, this is the same as dygraphs' internal format. If the x values
-3430  * are dates, we need to convert them from Date objects to ms since epoch.
-3431  * @param {[Object]} data
-3432  * @return {[Object]} data with numeric x values.
-3433  */
-3434 Dygraph.prototype.parseArray_ = function(data) {
-3435   // Peek at the first x value to see if it's numeric.
-3436   if (data.length == 0) {
-3437     this.error("Can't plot empty data set");
-3438     return null;
-3439   }
-3440   if (data[0].length == 0) {
-3441     this.error("Data set cannot contain an empty row");
-3442     return null;
-3443   }
-3444 
-3445   if (this.attr_("labels") == null) {
-3446     this.warn("Using default labels. Set labels explicitly via 'labels' " +
-3447               "in the options parameter");
-3448     this.attrs_.labels = [ "X" ];
-3449     for (var i = 1; i < data[0].length; i++) {
-3450       this.attrs_.labels.push("Y" + i);
-3451     }
-3452   }
-3453 
-3454   if (Dygraph.isDateLike(data[0][0])) {
-3455     // Some intelligent defaults for a date x-axis.
-3456     this.attrs_.xValueFormatter = Dygraph.dateString_;
-3457     this.attrs_.xAxisLabelFormatter = Dygraph.dateAxisFormatter;
-3458     this.attrs_.xTicker = Dygraph.dateTicker;
-3459 
-3460     // Assume they're all dates.
-3461     var parsedData = Dygraph.clone(data);
-3462     for (var i = 0; i < data.length; i++) {
-3463       if (parsedData[i].length == 0) {
-3464         this.error("Row " + (1 + i) + " of data is empty");
-3465         return null;
-3466       }
-3467       if (parsedData[i][0] == null
-3468           || typeof(parsedData[i][0].getTime) != 'function'
-3469           || isNaN(parsedData[i][0].getTime())) {
-3470         this.error("x value in row " + (1 + i) + " is not a Date");
-3471         return null;
-3472       }
-3473       parsedData[i][0] = parsedData[i][0].getTime();
-3474     }
-3475     return parsedData;
-3476   } else {
-3477     // Some intelligent defaults for a numeric x-axis.
-3478     this.attrs_.xValueFormatter = function(x) { return x; };
-3479     this.attrs_.xTicker = Dygraph.numericTicks;
-3480     return data;
-3481   }
-3482 };
-3483 
-3484 /**
-3485  * Parses a DataTable object from gviz.
-3486  * The data is expected to have a first column that is either a date or a
-3487  * number. All subsequent columns must be numbers. If there is a clear mismatch
-3488  * between this.xValueParser_ and the type of the first column, it will be
-3489  * fixed. Fills out rawData_.
-3490  * @param {[Object]} data See above.
-3491  * @private
-3492  */
-3493 Dygraph.prototype.parseDataTable_ = function(data) {
-3494   var cols = data.getNumberOfColumns();
-3495   var rows = data.getNumberOfRows();
-3496 
-3497   var indepType = data.getColumnType(0);
-3498   if (indepType == 'date' || indepType == 'datetime') {
-3499     this.attrs_.xValueFormatter = Dygraph.dateString_;
-3500     this.attrs_.xValueParser = Dygraph.dateParser;
-3501     this.attrs_.xTicker = Dygraph.dateTicker;
-3502     this.attrs_.xAxisLabelFormatter = Dygraph.dateAxisFormatter;
-3503   } else if (indepType == 'number') {
-3504     this.attrs_.xValueFormatter = function(x) { return x; };
-3505     this.attrs_.xValueParser = function(x) { return parseFloat(x); };
-3506     this.attrs_.xTicker = Dygraph.numericTicks;
-3507     this.attrs_.xAxisLabelFormatter = this.attrs_.xValueFormatter;
-3508   } else {
-3509     this.error("only 'date', 'datetime' and 'number' types are supported for " +
-3510                "column 1 of DataTable input (Got '" + indepType + "')");
-3511     return null;
-3512   }
-3513 
-3514   // Array of the column indices which contain data (and not annotations).
-3515   var colIdx = [];
-3516   var annotationCols = {};  // data index -> [annotation cols]
-3517   var hasAnnotations = false;
-3518   for (var i = 1; i < cols; i++) {
-3519     var type = data.getColumnType(i);
-3520     if (type == 'number') {
-3521       colIdx.push(i);
-3522     } else if (type == 'string' && this.attr_('displayAnnotations')) {
-3523       // This is OK -- it's an annotation column.
-3524       var dataIdx = colIdx[colIdx.length - 1];
-3525       if (!annotationCols.hasOwnProperty(dataIdx)) {
-3526         annotationCols[dataIdx] = [i];
-3527       } else {
-3528         annotationCols[dataIdx].push(i);
-3529       }
-3530       hasAnnotations = true;
-3531     } else {
-3532       this.error("Only 'number' is supported as a dependent type with Gviz." +
-3533                  " 'string' is only supported if displayAnnotations is true");
-3534     }
-3535   }
-3536 
-3537   // Read column labels
-3538   // TODO(danvk): add support back for errorBars
-3539   var labels = [data.getColumnLabel(0)];
-3540   for (var i = 0; i < colIdx.length; i++) {
-3541     labels.push(data.getColumnLabel(colIdx[i]));
-3542     if (this.attr_("errorBars")) i += 1;
-3543   }
-3544   this.attrs_.labels = labels;
-3545   cols = labels.length;
-3546 
-3547   var ret = [];
-3548   var outOfOrder = false;
-3549   var annotations = [];
-3550   for (var i = 0; i < rows; i++) {
-3551     var row = [];
-3552     if (typeof(data.getValue(i, 0)) === 'undefined' ||
-3553         data.getValue(i, 0) === null) {
-3554       this.warn("Ignoring row " + i +
-3555                 " of DataTable because of undefined or null first column.");
-3556       continue;
-3557     }
-3558 
-3559     if (indepType == 'date' || indepType == 'datetime') {
-3560       row.push(data.getValue(i, 0).getTime());
-3561     } else {
-3562       row.push(data.getValue(i, 0));
-3563     }
-3564     if (!this.attr_("errorBars")) {
-3565       for (var j = 0; j < colIdx.length; j++) {
-3566         var col = colIdx[j];
-3567         row.push(data.getValue(i, col));
-3568         if (hasAnnotations &&
-3569             annotationCols.hasOwnProperty(col) &&
-3570             data.getValue(i, annotationCols[col][0]) != null) {
-3571           var ann = {};
-3572           ann.series = data.getColumnLabel(col);
-3573           ann.xval = row[0];
-3574           ann.shortText = String.fromCharCode(65 /* A */ + annotations.length)
-3575           ann.text = '';
-3576           for (var k = 0; k < annotationCols[col].length; k++) {
-3577             if (k) ann.text += "\n";
-3578             ann.text += data.getValue(i, annotationCols[col][k]);
-3579           }
-3580           annotations.push(ann);
-3581         }
-3582       }
-3583 
-3584       // Strip out infinities, which give dygraphs problems later on.
-3585       for (var j = 0; j < row.length; j++) {
-3586         if (!isFinite(row[j])) row[j] = null;
-3587       }
-3588     } else {
-3589       for (var j = 0; j < cols - 1; j++) {
-3590         row.push([ data.getValue(i, 1 + 2 * j), data.getValue(i, 2 + 2 * j) ]);
-3591       }
-3592     }
-3593     if (ret.length > 0 && row[0] < ret[ret.length - 1][0]) {
-3594       outOfOrder = true;
-3595     }
-3596     ret.push(row);
-3597   }
-3598 
-3599   if (outOfOrder) {
-3600     this.warn("DataTable is out of order; order it correctly to speed loading.");
-3601     ret.sort(function(a,b) { return a[0] - b[0] });
-3602   }
-3603   this.rawData_ = ret;
-3604 
-3605   if (annotations.length > 0) {
-3606     this.setAnnotations(annotations, true);
-3607   }
-3608 }
-3609 
-3610 /**
-3611  * @private
-3612  * This is identical to JavaScript's built-in Date.parse() method, except that
-3613  * it doesn't get replaced with an incompatible method by aggressive JS
-3614  * libraries like MooTools or Joomla.
-3615  * @param { String } str The date string, e.g. "2011/05/06"
-3616  * @return { Integer } millis since epoch
-3617  */
-3618 Dygraph.dateStrToMillis = function(str) {
-3619   return new Date(str).getTime();
-3620 };
-3621 
-3622 // These functions are all based on MochiKit.
-3623 /**
-3624  * @private
-3625  */
-3626 Dygraph.update = function (self, o) {
-3627   if (typeof(o) != 'undefined' && o !== null) {
-3628     for (var k in o) {
-3629       if (o.hasOwnProperty(k)) {
-3630         self[k] = o[k];
-3631       }
-3632     }
-3633   }
-3634   return self;
-3635 };
-3636 
-3637 /**
-3638  * @private
-3639  */
-3640 Dygraph.isArrayLike = function (o) {
-3641   var typ = typeof(o);
-3642   if (
-3643       (typ != 'object' && !(typ == 'function' &&
-3644         typeof(o.item) == 'function')) ||
-3645       o === null ||
-3646       typeof(o.length) != 'number' ||
-3647       o.nodeType === 3
-3648      ) {
-3649     return false;
-3650   }
-3651   return true;
-3652 };
-3653 
-3654 /**
-3655  * @private
-3656  */
-3657 Dygraph.isDateLike = function (o) {
-3658   if (typeof(o) != "object" || o === null ||
-3659       typeof(o.getTime) != 'function') {
-3660     return false;
-3661   }
-3662   return true;
-3663 };
-3664 
-3665 /**
-3666  * @private
-3667  */
-3668 Dygraph.clone = function(o) {
-3669   // TODO(danvk): figure out how MochiKit's version works
-3670   var r = [];
-3671   for (var i = 0; i < o.length; i++) {
-3672     if (Dygraph.isArrayLike(o[i])) {
-3673       r.push(Dygraph.clone(o[i]));
-3674     } else {
-3675       r.push(o[i]);
-3676     }
-3677   }
-3678   return r;
-3679 };
-3680 
-3681 
-3682 /**
-3683  * Get the CSV data. If it's in a function, call that function. If it's in a
-3684  * file, do an XMLHttpRequest to get it.
-3685  * @private
-3686  */
-3687 Dygraph.prototype.start_ = function() {
-3688   if (typeof this.file_ == 'function') {
-3689     // CSV string. Pretend we got it via XHR.
-3690     this.loadedEvent_(this.file_());
-3691   } else if (Dygraph.isArrayLike(this.file_)) {
-3692     this.rawData_ = this.parseArray_(this.file_);
-3693     this.predraw_();
-3694   } else if (typeof this.file_ == 'object' &&
-3695              typeof this.file_.getColumnRange == 'function') {
-3696     // must be a DataTable from gviz.
-3697     this.parseDataTable_(this.file_);
-3698     this.predraw_();
-3699   } else if (typeof this.file_ == 'string') {
-3700     // Heuristic: a newline means it's CSV data. Otherwise it's an URL.
-3701     if (this.file_.indexOf('\n') >= 0) {
-3702       this.loadedEvent_(this.file_);
-3703     } else {
-3704       var req = new XMLHttpRequest();
-3705       var caller = this;
-3706       req.onreadystatechange = function () {
-3707         if (req.readyState == 4) {
-3708           if (req.status == 200) {
-3709             caller.loadedEvent_(req.responseText);
-3710           }
-3711         }
-3712       };
-3713 
-3714       req.open("GET", this.file_, true);
-3715       req.send(null);
-3716     }
-3717   } else {
-3718     this.error("Unknown data format: " + (typeof this.file_));
-3719   }
-3720 };
-3721 
-3722 /**
-3723  * Changes various properties of the graph. These can include:
-3724  * <ul>
-3725  * <li>file: changes the source data for the graph</li>
-3726  * <li>errorBars: changes whether the data contains stddev</li>
-3727  * </ul>
-3728  *
-3729  * @param {Object} attrs The new properties and values
-3730  */
-3731 Dygraph.prototype.updateOptions = function(attrs) {
-3732   // TODO(danvk): this is a mess. Rethink this function.
-3733   if ('rollPeriod' in attrs) {
-3734     this.rollPeriod_ = attrs.rollPeriod;
-3735   }
-3736   if ('dateWindow' in attrs) {
-3737     this.dateWindow_ = attrs.dateWindow;
-3738     if (!('isZoomedIgnoreProgrammaticZoom' in attrs)) {
-3739       this.zoomed_x_ = attrs.dateWindow != null;
-3740     }
-3741   }
-3742   if ('valueRange' in attrs && !('isZoomedIgnoreProgrammaticZoom' in attrs)) {
-3743     this.zoomed_y_ = attrs.valueRange != null;
-3744   }
-3745 
-3746   // TODO(danvk): validate per-series options.
-3747   // Supported:
-3748   // strokeWidth
-3749   // pointSize
-3750   // drawPoints
-3751   // highlightCircleSize
-3752 
-3753   Dygraph.update(this.user_attrs_, attrs);
-3754   Dygraph.update(this.renderOptions_, attrs);
-3755 
-3756   this.labelsFromCSV_ = (this.attr_("labels") == null);
-3757 
-3758   // TODO(danvk): this doesn't match the constructor logic
-3759   this.layout_.updateOptions({ 'errorBars': this.attr_("errorBars") });
-3760   if (attrs['file']) {
-3761     this.file_ = attrs['file'];
-3762     this.start_();
-3763   } else {
-3764     this.predraw_();
-3765   }
-3766 };
-3767 
-3768 /**
-3769  * Resizes the dygraph. If no parameters are specified, resizes to fill the
-3770  * containing div (which has presumably changed size since the dygraph was
-3771  * instantiated. If the width/height are specified, the div will be resized.
-3772  *
-3773  * This is far more efficient than destroying and re-instantiating a
-3774  * Dygraph, since it doesn't have to reparse the underlying data.
-3775  *
-3776  * @param {Number} [width] Width (in pixels)
-3777  * @param {Number} [height] Height (in pixels)
-3778  */
-3779 Dygraph.prototype.resize = function(width, height) {
-3780   if (this.resize_lock) {
-3781     return;
-3782   }
-3783   this.resize_lock = true;
-3784 
-3785   if ((width === null) != (height === null)) {
-3786     this.warn("Dygraph.resize() should be called with zero parameters or " +
-3787               "two non-NULL parameters. Pretending it was zero.");
-3788     width = height = null;
-3789   }
-3790 
-3791   // TODO(danvk): there should be a clear() method.
-3792   this.maindiv_.innerHTML = "";
-3793   this.attrs_.labelsDiv = null;
-3794 
-3795   if (width) {
-3796     this.maindiv_.style.width = width + "px";
-3797     this.maindiv_.style.height = height + "px";
-3798     this.width_ = width;
-3799     this.height_ = height;
-3800   } else {
-3801     this.width_ = this.maindiv_.offsetWidth;
-3802     this.height_ = this.maindiv_.offsetHeight;
-3803   }
-3804 
-3805   this.createInterface_();
-3806   this.predraw_();
-3807 
-3808   this.resize_lock = false;
-3809 };
-3810 
-3811 /**
-3812  * Adjusts the number of points in the rolling average. Updates the graph to
-3813  * reflect the new averaging period.
-3814  * @param {Number} length Number of points over which to average the data.
-3815  */
-3816 Dygraph.prototype.adjustRoll = function(length) {
-3817   this.rollPeriod_ = length;
-3818   this.predraw_();
-3819 };
-3820 
-3821 /**
-3822  * Returns a boolean array of visibility statuses.
-3823  */
-3824 Dygraph.prototype.visibility = function() {
-3825   // Do lazy-initialization, so that this happens after we know the number of
-3826   // data series.
-3827   if (!this.attr_("visibility")) {
-3828     this.attrs_["visibility"] = [];
-3829   }
-3830   while (this.attr_("visibility").length < this.rawData_[0].length - 1) {
-3831     this.attr_("visibility").push(true);
-3832   }
-3833   return this.attr_("visibility");
-3834 };
-3835 
-3836 /**
-3837  * Changes the visiblity of a series.
-3838  */
-3839 Dygraph.prototype.setVisibility = function(num, value) {
-3840   var x = this.visibility();
-3841   if (num < 0 || num >= x.length) {
-3842     this.warn("invalid series number in setVisibility: " + num);
-3843   } else {
-3844     x[num] = value;
-3845     this.predraw_();
-3846   }
-3847 };
-3848 
-3849 /**
-3850  * Update the list of annotations and redraw the chart.
-3851  */
-3852 Dygraph.prototype.setAnnotations = function(ann, suppressDraw) {
-3853   // Only add the annotation CSS rule once we know it will be used.
-3854   Dygraph.addAnnotationRule();
-3855   this.annotations_ = ann;
-3856   this.layout_.setAnnotations(this.annotations_);
-3857   if (!suppressDraw) {
-3858     this.predraw_();
-3859   }
-3860 };
-3861 
-3862 /**
-3863  * Return the list of annotations.
-3864  */
-3865 Dygraph.prototype.annotations = function() {
-3866   return this.annotations_;
-3867 };
-3868 
-3869 /**
-3870  * Get the index of a series (column) given its name. The first column is the
-3871  * x-axis, so the data series start with index 1.
-3872  */
-3873 Dygraph.prototype.indexFromSetName = function(name) {
-3874   var labels = this.attr_("labels");
-3875   for (var i = 0; i < labels.length; i++) {
-3876     if (labels[i] == name) return i;
-3877   }
-3878   return null;
-3879 };
-3880 
-3881 /**
-3882  * @private
-3883  * Adds a default style for the annotation CSS classes to the document. This is
-3884  * only executed when annotations are actually used. It is designed to only be
-3885  * called once -- all calls after the first will return immediately.
-3886  */
-3887 Dygraph.addAnnotationRule = function() {
-3888   if (Dygraph.addedAnnotationCSS) return;
-3889 
-3890   var rule = "border: 1px solid black; " +
-3891              "background-color: white; " +
-3892              "text-align: center;";
-3893 
-3894   var styleSheetElement = document.createElement("style");
-3895   styleSheetElement.type = "text/css";
-3896   document.getElementsByTagName("head")[0].appendChild(styleSheetElement);
-3897 
-3898   // Find the first style sheet that we can access.
-3899   // We may not add a rule to a style sheet from another domain for security
-3900   // reasons. This sometimes comes up when using gviz, since the Google gviz JS
-3901   // adds its own style sheets from google.com.
-3902   for (var i = 0; i < document.styleSheets.length; i++) {
-3903     if (document.styleSheets[i].disabled) continue;
-3904     var mysheet = document.styleSheets[i];
-3905     try {
-3906       if (mysheet.insertRule) {  // Firefox
-3907         var idx = mysheet.cssRules ? mysheet.cssRules.length : 0;
-3908         mysheet.insertRule(".dygraphDefaultAnnotation { " + rule + " }", idx);
-3909       } else if (mysheet.addRule) {  // IE
-3910         mysheet.addRule(".dygraphDefaultAnnotation", rule);
-3911       }
-3912       Dygraph.addedAnnotationCSS = true;
-3913       return;
-3914     } catch(err) {
-3915       // Was likely a security exception.
-3916     }
-3917   }
-3918 
-3919   this.warn("Unable to add default annotation CSS rule; display may be off.");
-3920 }
-3921 
-3922 /**
-3923  * @private
-3924  * Create a new canvas element. This is more complex than a simple
-3925  * document.createElement("canvas") because of IE and excanvas.
-3926  */
-3927 Dygraph.createCanvas = function() {
-3928   var canvas = document.createElement("canvas");
-3929 
-3930   isIE = (/MSIE/.test(navigator.userAgent) && !window.opera);
-3931   if (isIE && (typeof(G_vmlCanvasManager) != 'undefined')) {
-3932     canvas = G_vmlCanvasManager.initElement(canvas);
-3933   }
-3934 
-3935   return canvas;
-3936 };
-3937 
-3938 
-3939 /**
-3940  * A wrapper around Dygraph that implements the gviz API.
-3941  * @param {Object} container The DOM object the visualization should live in.
-3942  */
-3943 Dygraph.GVizChart = function(container) {
-3944   this.container = container;
-3945 }
-3946 
-3947 Dygraph.GVizChart.prototype.draw = function(data, options) {
-3948   // Clear out any existing dygraph.
-3949   // TODO(danvk): would it make more sense to simply redraw using the current
-3950   // date_graph object?
-3951   this.container.innerHTML = '';
-3952   if (typeof(this.date_graph) != 'undefined') {
-3953     this.date_graph.destroy();
-3954   }
-3955 
-3956   this.date_graph = new Dygraph(this.container, data, options);
-3957 }
-3958 
-3959 /**
-3960  * Google charts compatible setSelection
-3961  * Only row selection is supported, all points in the row will be highlighted
-3962  * @param {Array} array of the selected cells
-3963  * @public
-3964  */
-3965 Dygraph.GVizChart.prototype.setSelection = function(selection_array) {
-3966   var row = false;
-3967   if (selection_array.length) {
-3968     row = selection_array[0].row;
-3969   }
-3970   this.date_graph.setSelection(row);
-3971 }
-3972 
-3973 /**
-3974  * Google charts compatible getSelection implementation
-3975  * @return {Array} array of the selected cells
-3976  * @public
-3977  */
-3978 Dygraph.GVizChart.prototype.getSelection = function() {
-3979   var selection = [];
-3980 
-3981   var row = this.date_graph.getSelection();
-3982 
-3983   if (row < 0) return selection;
-3984 
-3985   col = 1;
-3986   for (var i in this.date_graph.layout_.datasets) {
-3987     selection.push({row: row, column: col});
-3988     col++;
-3989   }
-3990 
-3991   return selection;
-3992 }
-3993 
-3994 // Older pages may still use this name.
-3995 DateGraph = Dygraph;
-3996 
-3997 // <REMOVE_FOR_COMBINED>
-3998 Dygraph.OPTIONS_REFERENCE =  // <JSON>
-3999 {
-4000   "xValueParser": {
-4001     "default": "parseFloat() or Date.parse()*",
-4002     "labels": ["CSV parsing"],
-4003     "type": "function(str) -> number",
-4004     "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."
-4005   },
-4006   "stackedGraph": {
-4007     "default": "false",
-4008     "labels": ["Data Line display"],
-4009     "type": "boolean",
-4010     "description": "If set, stack series on top of one another rather than drawing them independently."
-4011   },
-4012   "pointSize": {
-4013     "default": "1",
-4014     "labels": ["Data Line display"],
-4015     "type": "integer",
-4016     "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."
-4017   },
-4018   "labelsDivStyles": {
-4019     "default": "null",
-4020     "labels": ["Legend"],
-4021     "type": "{}",
-4022     "description": "Additional styles to apply to the currently-highlighted points div. For example, { 'font-weight': 'bold' } will make the labels bold."
-4023   },
-4024   "drawPoints": {
-4025     "default": "false",
-4026     "labels": ["Data Line display"],
-4027     "type": "boolean",
-4028     "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."
-4029   },
-4030   "height": {
-4031     "default": "320",
-4032     "labels": ["Overall display"],
-4033     "type": "integer",
-4034     "description": "Height, in pixels, of the chart. If the container div has been explicitly sized, this will be ignored."
-4035   },
-4036   "zoomCallback": {
-4037     "default": "null",
-4038     "labels": ["Callbacks"],
-4039     "type": "function(minDate, maxDate, yRanges)",
-4040     "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."
-4041   },
-4042   "pointClickCallback": {
-4043     "default": "",
-4044     "labels": ["Callbacks", "Interactive Elements"],
-4045     "type": "",
-4046     "description": ""
-4047   },
-4048   "colors": {
-4049     "default": "(see description)",
-4050     "labels": ["Data Series Colors"],
-4051     "type": "array<string>",
-4052     "example": "['red', '#00FF00']",
-4053     "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."
-4054   },
-4055   "connectSeparatedPoints": {
-4056     "default": "false",
-4057     "labels": ["Data Line display"],
-4058     "type": "boolean",
-4059     "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."
-4060   },
-4061   "highlightCallback": {
-4062     "default": "null",
-4063     "labels": ["Callbacks"],
-4064     "type": "function(event, x, points,row)",
-4065     "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>"
-4066   },
-4067   "includeZero": {
-4068     "default": "false",
-4069     "labels": ["Axis display"],
-4070     "type": "boolean",
-4071     "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"
-4072   },
-4073   "rollPeriod": {
-4074     "default": "1",
-4075     "labels": ["Error Bars", "Rolling Averages"],
-4076     "type": "integer >= 1",
-4077     "description": "Number of days over which to average data. Discussed extensively above."
-4078   },
-4079   "unhighlightCallback": {
-4080     "default": "null",
-4081     "labels": ["Callbacks"],
-4082     "type": "function(event)",
-4083     "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."
-4084   },
-4085   "axisTickSize": {
-4086     "default": "3.0",
-4087     "labels": ["Axis display"],
-4088     "type": "number",
-4089     "description": "The size of the line to display next to each tick mark on x- or y-axes."
-4090   },
-4091   "labelsSeparateLines": {
-4092     "default": "false",
-4093     "labels": ["Legend"],
-4094     "type": "boolean",
-4095     "description": "Put <code><br/></code> between lines in the label string. Often used in conjunction with <strong>labelsDiv</strong>."
-4096   },
-4097   "xValueFormatter": {
-4098     "default": "(Round to 2 decimal places)",
-4099     "labels": ["Axis display"],
-4100     "type": "function(x)",
-4101     "description": "Function to provide a custom display format for the X value for mouseover."
-4102   },
-4103   "pixelsPerYLabel": {
-4104     "default": "30",
-4105     "labels": ["Axis display", "Grid"],
-4106     "type": "integer",
-4107     "description": "Number of pixels to require between each x- and y-label. Larger values will yield a sparser axis with fewer ticks."
-4108   },
-4109   "annotationMouseOverHandler": {
-4110     "default": "null",
-4111     "labels": ["Annotations"],
-4112     "type": "function(annotation, point, dygraph, event)",
-4113     "description": "If provided, this function is called whenever the user mouses over an annotation."
-4114   },
-4115   "annotationMouseOutHandler": {
-4116     "default": "null",
-4117     "labels": ["Annotations"],
-4118     "type": "function(annotation, point, dygraph, event)",
-4119     "description": "If provided, this function is called whenever the user mouses out of an annotation."
-4120   },
-4121   "annotationClickHandler": {
-4122     "default": "null",
-4123     "labels": ["Annotations"],
-4124     "type": "function(annotation, point, dygraph, event)",
-4125     "description": "If provided, this function is called whenever the user clicks on an annotation."
-4126   },
-4127   "annotationDblClickHandler": {
-4128     "default": "null",
-4129     "labels": ["Annotations"],
-4130     "type": "function(annotation, point, dygraph, event)",
-4131     "description": "If provided, this function is called whenever the user double-clicks on an annotation."
-4132   },
-4133   "drawCallback": {
-4134     "default": "null",
-4135     "labels": ["Callbacks"],
-4136     "type": "function(dygraph, is_initial)",
-4137     "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."
-4138   },
-4139   "labelsKMG2": {
-4140     "default": "false",
-4141     "labels": ["Value display/formatting"],
-4142     "type": "boolean",
-4143     "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."
-4144   },
-4145   "delimiter": {
-4146     "default": ",",
-4147     "labels": ["CSV parsing"],
-4148     "type": "string",
-4149     "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."
-4150   },
-4151   "axisLabelFontSize": {
-4152     "default": "14",
-4153     "labels": ["Axis display"],
-4154     "type": "integer",
-4155     "description": "Size of the font (in pixels) to use in the axis labels, both x- and y-axis."
-4156   },
-4157   "underlayCallback": {
-4158     "default": "null",
-4159     "labels": ["Callbacks"],
-4160     "type": "function(canvas, area, dygraph)",
-4161     "description": "When set, this callback gets called before the chart is drawn. It details on how to use this."
-4162   },
-4163   "width": {
-4164     "default": "480",
-4165     "labels": ["Overall display"],
-4166     "type": "integer",
-4167     "description": "Width, in pixels, of the chart. If the container div has been explicitly sized, this will be ignored."
-4168   },
-4169   "interactionModel": {
-4170     "default": "...",
-4171     "labels": ["Interactive Elements"],
-4172     "type": "Object",
-4173     "description": "TODO(konigsberg): document this"
-4174   },
-4175   "xTicker": {
-4176     "default": "Dygraph.dateTicker or Dygraph.numericTicks",
-4177     "labels": ["Axis display"],
-4178     "type": "function(min, max, dygraph) -> [{v: ..., label: ...}, ...]",
-4179     "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."
-4180   },
-4181   "xAxisLabelWidth": {
-4182     "default": "50",
-4183     "labels": ["Axis display"],
-4184     "type": "integer",
-4185     "description": "Width, in pixels, of the x-axis labels."
-4186   },
-4187   "showLabelsOnHighlight": {
-4188     "default": "true",
-4189     "labels": ["Interactive Elements", "Legend"],
-4190     "type": "boolean",
-4191     "description": "Whether to show the legend upon mouseover."
-4192   },
-4193   "axis": {
-4194     "default": "(none)",
-4195     "labels": ["Axis display"],
-4196     "type": "string or object",
-4197     "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."
-4198   },
-4199   "pixelsPerXLabel": {
-4200     "default": "60",
-4201     "labels": ["Axis display", "Grid"],
-4202     "type": "integer",
-4203     "description": "Number of pixels to require between each x- and y-label. Larger values will yield a sparser axis with fewer ticks."
-4204   },
-4205   "labelsDiv": {
-4206     "default": "null",
-4207     "labels": ["Legend"],
-4208     "type": "DOM element or string",
-4209     "example": "<code style='font-size: small'>document.getElementById('foo')</code>or<code>'foo'",
-4210     "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."
-4211   },
-4212   "fractions": {
-4213     "default": "false",
-4214     "labels": ["CSV parsing", "Error Bars"],
-4215     "type": "boolean",
-4216     "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)."
-4217   },
-4218   "logscale": {
-4219     "default": "false",
-4220     "labels": ["Axis display"],
-4221     "type": "boolean",
-4222     "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."
-4223   },
-4224   "strokeWidth": {
-4225     "default": "1.0",
-4226     "labels": ["Data Line display"],
-4227     "type": "integer",
-4228     "example": "0.5, 2.0",
-4229     "description": "The width of the lines connecting data points. This can be used to increase the contrast or some graphs."
-4230   },
-4231   "wilsonInterval": {
-4232     "default": "true",
-4233     "labels": ["Error Bars"],
-4234     "type": "boolean",
-4235     "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."
-4236   },
-4237   "fillGraph": {
-4238     "default": "false",
-4239     "labels": ["Data Line display"],
-4240     "type": "boolean",
-4241     "description": "Should the area underneath the graph be filled? This option is not compatible with error bars."
-4242   },
-4243   "highlightCircleSize": {
-4244     "default": "3",
-4245     "labels": ["Interactive Elements"],
-4246     "type": "integer",
-4247     "description": "The size in pixels of the dot drawn over highlighted points."
-4248   },
-4249   "gridLineColor": {
-4250     "default": "rgb(128,128,128)",
-4251     "labels": ["Grid"],
-4252     "type": "red, blue",
-4253     "description": "The color of the gridlines."
-4254   },
-4255   "visibility": {
-4256     "default": "[true, true, ...]",
-4257     "labels": ["Data Line display"],
-4258     "type": "Array of booleans",
-4259     "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."
-4260   },
-4261   "valueRange": {
-4262     "default": "Full range of the input is shown",
-4263     "labels": ["Axis display"],
-4264     "type": "Array of two numbers",
-4265     "example": "[10, 110]",
-4266     "description": "Explicitly set the vertical range of the graph to [low, high]."
-4267   },
-4268   "labelsDivWidth": {
-4269     "default": "250",
-4270     "labels": ["Legend"],
-4271     "type": "integer",
-4272     "description": "Width (in pixels) of the div which shows information on the currently-highlighted points."
-4273   },
-4274   "colorSaturation": {
-4275     "default": "1.0",
-4276     "labels": ["Data Series Colors"],
-4277     "type": "0.0 - 1.0",
-4278     "description": "If <strong>colors</strong> is not specified, saturation of the automatically-generated data series colors."
-4279   },
-4280   "yAxisLabelWidth": {
-4281     "default": "50",
-4282     "labels": ["Axis display"],
-4283     "type": "integer",
-4284     "description": "Width, in pixels, of the y-axis labels."
-4285   },
-4286   "hideOverlayOnMouseOut": {
-4287     "default": "true",
-4288     "labels": ["Interactive Elements", "Legend"],
-4289     "type": "boolean",
-4290     "description": "Whether to hide the legend when the mouse leaves the chart area."
-4291   },
-4292   "yValueFormatter": {
-4293     "default": "(Round to 2 decimal places)",
-4294     "labels": ["Axis display"],
-4295     "type": "function(x)",
-4296     "description": "Function to provide a custom display format for the Y value for mouseover."
-4297   },
-4298   "legend": {
-4299     "default": "onmouseover",
-4300     "labels": ["Legend"],
-4301     "type": "string",
-4302     "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."
-4303   },
-4304   "labelsShowZeroValues": {
-4305     "default": "true",
-4306     "labels": ["Legend"],
-4307     "type": "boolean",
-4308     "description": "Show zero value labels in the labelsDiv."
-4309   },
-4310   "stepPlot": {
-4311     "default": "false",
-4312     "labels": ["Data Line display"],
-4313     "type": "boolean",
-4314     "description": "When set, display the graph as a step plot instead of a line plot."
-4315   },
-4316   "labelsKMB": {
-4317     "default": "false",
-4318     "labels": ["Value display/formatting"],
-4319     "type": "boolean",
-4320     "description": "Show K/M/B for thousands/millions/billions on y-axis."
-4321   },
-4322   "rightGap": {
-4323     "default": "5",
-4324     "labels": ["Overall display"],
-4325     "type": "integer",
-4326     "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."
-4327   },
-4328   "avoidMinZero": {
-4329     "default": "false",
-4330     "labels": ["Axis display"],
-4331     "type": "boolean",
-4332     "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."
-4333   },
-4334   "xAxisLabelFormatter": {
-4335     "default": "Dygraph.dateAxisFormatter",
-4336     "labels": ["Axis display", "Value display/formatting"],
-4337     "type": "function(date, granularity)",
-4338     "description": "Function to call to format values along the x axis."
-4339   },
-4340   "clickCallback": {
-4341     "snippet": "function(e, date){<br>  alert(date);<br>}",
-4342     "default": "null",
-4343     "labels": ["Callbacks"],
-4344     "type": "function(e, date)",
-4345     "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."
-4346   },
-4347   "yAxisLabelFormatter": {
-4348     "default": "yValueFormatter",
-4349     "labels": ["Axis display", "Value display/formatting"],
-4350     "type": "function(x)",
-4351     "description": "Function used to format values along the Y axis. By default it uses the same as the <code>yValueFormatter</code> unless specified."
-4352   },
-4353   "labels": {
-4354     "default": "[\"X\", \"Y1\", \"Y2\", ...]*",
-4355     "labels": ["Legend"],
-4356     "type": "array<string>",
-4357     "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."
-4358   },
-4359   "dateWindow": {
-4360     "default": "Full range of the input is shown",
-4361     "labels": ["Axis display"],
-4362     "type": "Array of two Dates or numbers",
-4363     "example": "[<br>  Date.parse('2006-01-01'),<br>  (new Date()).valueOf()<br>]",
-4364     "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."
-4365   },
-4366   "showRoller": {
-4367     "default": "false",
-4368     "labels": ["Interactive Elements", "Rolling Averages"],
-4369     "type": "boolean",
-4370     "description": "If the rolling average period text box should be shown."
-4371   },
-4372   "sigma": {
-4373     "default": "2.0",
-4374     "labels": ["Error Bars"],
-4375     "type": "float",
-4376     "description": "When errorBars is set, shade this many standard deviations above/below each point."
-4377   },
-4378   "customBars": {
-4379     "default": "false",
-4380     "labels": ["CSV parsing", "Error Bars"],
-4381     "type": "boolean",
-4382     "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."
-4383   },
-4384   "colorValue": {
-4385     "default": "1.0",
-4386     "labels": ["Data Series Colors"],
-4387     "type": "float (0.0 - 1.0)",
-4388     "description": "If colors is not specified, value of the data series colors, as in hue/saturation/value. (0.0-1.0, default 0.5)"
-4389   },
-4390   "errorBars": {
-4391     "default": "false",
-4392     "labels": ["CSV parsing", "Error Bars"],
-4393     "type": "boolean",
-4394     "description": "Does the data contain standard deviations? Setting this to true alters the input format (see above)."
-4395   },
-4396   "displayAnnotations": {
-4397     "default": "false",
-4398     "labels": ["Annotations"],
-4399     "type": "boolean",
-4400     "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."
-4401   },
-4402   "panEdgeFraction": {
-4403     "default": "null",
-4404     "labels": ["Axis Display", "Interactive Elements"],
-4405     "type": "float",
-4406     "default": "null",
-4407     "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."
-4408   },
-4409   "title": {
-4410     "labels": ["Chart labels"],
-4411     "type": "string",
-4412     "default": "null",
-4413     "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."
-4414   },
-4415   "titleHeight": {
-4416     "default": "18",
-4417     "labels": ["Chart labels"],
-4418     "type": "integer",
-4419     "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."
-4420   },
-4421   "xlabel": {
-4422     "labels": ["Chart labels"],
-4423     "type": "string",
-4424     "default": "null",
-4425     "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."
-4426   },
-4427   "xLabelHeight": {
-4428     "labels": ["Chart labels"],
-4429     "type": "integer",
-4430     "default": "18",
-4431     "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."
-4432   },
-4433   "ylabel": {
-4434     "labels": ["Chart labels"],
-4435     "type": "string",
-4436     "default": "null",
-4437     "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."
-4438   },
-4439   "yLabelWidth": {
-4440     "labels": ["Chart labels"],
-4441     "type": "integer",
-4442     "default": "18",
-4443     "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."
-4444   },
-4445   "isZoomedIgnoreProgrammaticZoom" : {
-4446     "default": "false",
-4447     "labels": ["Zooming"],
-4448     "type": "boolean",
-4449     "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."
-4450   },
-4451   "sigFigs" : {
-4452     "default": "null",
-4453     "labels": ["Value display/formatting"],
-4454     "type": "integer",
-4455     "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."
-4456   },
-4457   "digitsAfterDecimal" : {
-4458     "default": "2",
-4459     "labels": ["Value display/formatting"],
-4460     "type": "integer",
-4461     "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."
-4462   },
-4463   "maxNumberWidth" : {
-4464     "default": "6",
-4465     "labels": ["Value display/formatting"],
-4466     "type": "integer",
-4467     "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."
-4468   }
-4469 }
-4470 ;  // </JSON>
-4471 // NOTE: in addition to parsing as JS, this snippet is expected to be valid
-4472 // JSON. This assumption cannot be checked in JS, but it will be checked when
-4473 // documentation is generated by the generate-documentation.py script. For the
-4474 // most part, this just means that you should always use double quotes.
-4475 
-4476 // Do a quick sanity check on the options reference.
-4477 (function() {
-4478   var warn = function(msg) { if (console) console.warn(msg); };
-4479   var flds = ['type', 'default', 'description'];
-4480   var valid_cats = [ 
-4481    'Annotations',
-4482    'Axis display',
-4483    'Chart labels',
-4484    'CSV parsing',
-4485    'Callbacks',
-4486    'Data Line display',
-4487    'Data Series Colors',
-4488    'Error Bars',
-4489    'Grid',
-4490    'Interactive Elements',
-4491    'Legend',
-4492    'Overall display',
-4493    'Rolling Averages',
-4494    'Value display/formatting',
-4495    'Zooming'
-4496   ];
-4497   var cats = {};
-4498   for (var i = 0; i < valid_cats.length; i++) cats[valid_cats[i]] = true;
-4499 
-4500   for (var k in Dygraph.OPTIONS_REFERENCE) {
-4501     if (!Dygraph.OPTIONS_REFERENCE.hasOwnProperty(k)) continue;
-4502     var op = Dygraph.OPTIONS_REFERENCE[k];
-4503     for (var i = 0; i < flds.length; i++) {
-4504       if (!op.hasOwnProperty(flds[i])) {
-4505         warn('Option ' + k + ' missing "' + flds[i] + '" property');
-4506       } else if (typeof(op[flds[i]]) != 'string') {
-4507         warn(k + '.' + flds[i] + ' must be of type string');
-4508       }
-4509     }
-4510     var labels = op['labels'];
-4511     if (typeof(labels) !== 'object') {
-4512       warn('Option "' + k + '" is missing a "labels": [...] option');
-4513       for (var i = 0; i < labels.length; i++) {
-4514         if (!cats.hasOwnProperty(labels[i])) {
-4515           warn('Option "' + k + '" has label "' + labels[i] +
-4516                '", which is invalid.');
-4517         }
-4518       }
-4519     }
-4520   }
-4521 })();
-4522 // </REMOVE_FOR_COMBINED>
-4523 
\ No newline at end of file -- 2.7.4