Clean up gallery; show HTML, Javascript and CSS in prettier fashion.
<script type="text/javascript" src="../tests/update_options.js"></script>
<script type="text/javascript" src="../tests/utils_test.js"></script>
<script type="text/javascript" src="../tests/multiple_axes.js"></script>
+ <script type="text/javascript" src="../tests/callback.js"></script>
<script type="text/javascript">
g.setSelection(9);
assertEquals("vf9: y:vf18", getLegend());
};
+
+AxisLabelsTestCase.prototype.testSeriesOrder = function() {
+ var opts = {
+ width: 480,
+ height: 320
+ };
+ var data = "x,00,01,10,11\n" +
+ "0,101,201,301,401\n" +
+ "1,102,202,302,402\n" +
+ "2,103,203,303,403\n" +
+ "3,104,204,304,404\n"
+ ;
+
+ var graph = document.getElementById("graph");
+ var g = new Dygraph(graph, data, opts);
+
+ g.setSelection(2);
+ assertEquals('2: 00:103 01:203 10:303 11:403', getLegend());
+
+ // Sanity checks for indexFromSetName
+ assertEquals(0, g.indexFromSetName("x"));
+ assertEquals(1, g.indexFromSetName("00"));
+ assertEquals(null, g.indexFromSetName("abcde"));
+
+ // Verify that we get the label list back in the right order
+ assertEquals(["x", "00", "01", "10", "11"], g.getLabels());
+};
--- /dev/null
+/**
+ * @fileoverview Test cases for the callbacks.
+ *
+ * @author uemit.seren@gmail.com (Ümit Seren)
+ */
+
+var CallbackTestCase = TestCase("callback");
+
+CallbackTestCase.prototype.setUp = function() {
+ document.body.innerHTML = "<div id='graph'></div>";
+};
+
+CallbackTestCase.prototype.tearDown = function() {
+};
+
+var data = "X,a\,b,c\n" +
+ "10,-1,1,2\n" +
+ "11,0,3,1\n" +
+ "12,1,4,2\n" +
+ "13,0,2,3\n";
+
+
+/**
+ * This tests that when the function idxToRow_ returns the proper row and the onHiglightCallback
+ * is properly called when the first series is hidden (setVisibility = false)
+ *
+ */
+CallbackTestCase.prototype.testHighlightCallbackIsCalled = function() {
+ var h_row;
+ var h_pts;
+
+ var highlightCallback = function(e, x, pts, row) {
+ h_row = row;
+ h_pts = pts;
+ };
+
+ var graph = document.getElementById("graph");
+ var g = new Dygraph(graph, data,
+ {
+ width: 100,
+ height : 100,
+ visibility: [false, true, true],
+ highlightCallback : highlightCallback,
+ });
+
+ DygraphOps.dispatchMouseMove(g, 13, 10);
+
+ //check correct row is returned
+ assertEquals(3, h_row);
+ //check there are only two points (because first series is hidden)
+ assertEquals(2, h_pts.length);
+};
+
+
+/**
+ * Test that drawPointCallback isn't called when drawPoints is false
+ */
+CallbackTestCase.prototype.testDrawPointCallback_disabled = function() {
+ var called = false;
+
+ var callback = function() {
+ called = true;
+ };
+
+ var graph = document.getElementById("graph");
+ var g = new Dygraph(graph, data, {
+ drawPointCallback : callback,
+ });
+
+ assertFalse(called);
+};
+
+/**
+ * Test that drawPointCallback is called when drawPoints is true
+ */
+CallbackTestCase.prototype.testDrawPointCallback_enabled = function() {
+ var called = false;
+
+ var callback = function() {
+ called = true;
+ };
+
+ var graph = document.getElementById("graph");
+ var g = new Dygraph(graph, data, {
+ drawPoints : true,
+ drawPointCallback : callback
+ });
+
+ assertTrue(called);
+};
+
+/**
+ * Test that drawPointCallback is called when drawPoints is true
+ */
+CallbackTestCase.prototype.testDrawPointCallback_pointSize = function() {
+ var pointSize = 0;
+ var count = 0;
+
+ var callback = function(g, seriesName, canvasContext, cx, cy, color, pointSizeParam) {
+ pointSize = pointSizeParam;
+ count++;
+ };
+
+ var graph = document.getElementById("graph");
+ var g = new Dygraph(graph, data, {
+ drawPoints : true,
+ drawPointCallback : callback
+ });
+
+ assertEquals(1.5, pointSize);
+ assertEquals(12, count); // one call per data point.
+
+ var g = new Dygraph(graph, data, {
+ drawPoints : true,
+ drawPointCallback : callback,
+ pointSize : 8
+ });
+
+ assertEquals(8, pointSize);
+};
+
+/**
+ * This tests that when the function idxToRow_ returns the proper row and the onHiglightCallback
+ * is properly called when the first series is hidden (setVisibility = false)
+ *
+ */
+CallbackTestCase.prototype.testDrawHighlightPointCallbackIsCalled = function() {
+ var called = false;
+
+ var drawHighlightPointCallback = function() {
+ called = true;
+ };
+
+ var graph = document.getElementById("graph");
+ var g = new Dygraph(graph, data,
+ {
+ width: 100,
+ height : 100,
+ drawHighlightPointCallback : drawHighlightPointCallback
+ });
+
+ assertFalse(called);
+ DygraphOps.dispatchMouseMove(g, 13, 10);
+ assertTrue(called);
+};
assertTrue(g.isZoomed("x"));
assertTrue(g.isZoomed("y"));
};
+
+
+InteractionModelTestCase.prototype.testCorrectAxisValueRangeAfterUnzoom = function() {
+ var g = new Dygraph(document.getElementById("graph"), data2, {valueRange:[1,50],dateRange:[1,9],animatedZooms:false});
+
+ // Zoom x axis
+ DygraphOps.dispatchMouseDown_Point(g, 10, 10);
+ DygraphOps.dispatchMouseMove_Point(g, 30, 10);
+ DygraphOps.dispatchMouseUp_Point(g, 30, 10);
+
+ // Zoom y axis
+ DygraphOps.dispatchMouseDown_Point(g, 10, 10);
+ DygraphOps.dispatchMouseMove_Point(g, 10, 30);
+ DygraphOps.dispatchMouseUp_Point(g, 10, 30);
+ currentYAxisRange = g.yAxisRange();
+ currentXAxisRange = g.xAxisRange();
+
+ //check that the range for the axis has changed
+ assertNotEquals(1,currentXAxisRange[0]);
+ assertNotEquals(10,currentXAxisRange[1]);
+ assertNotEquals(1,currentYAxisRange[0]);
+ assertNotEquals(50,currentYAxisRange[1]);
+
+ // unzoom by doubleclick
+ DygraphOps.dispatchDoubleClick(g, null);
+
+ // check if range for y-axis was reset to original value
+ // TODO check if range for x-axis is correct.
+ // Currently not possible because dateRange is set to null and extremes are returned
+ newYAxisRange = g.yAxisRange();
+ assertEquals(1,newYAxisRange[0]);
+ assertEquals(50,newYAxisRange[1]);
+};
assertEquals(["y-axis"], getClassTexts("dygraph-ylabel"));
assertEquals([], getClassTexts("dygraph-y2label"));
};
+
+MultipleAxesTestCase.prototype.testValueRangePerAxisOptions = function() {
+ var data = MultipleAxesTestCase.getData();
+
+ g = new Dygraph(
+ document.getElementById("graph"),
+ data,
+ {
+ labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ],
+ 'Y3': {
+ axis: {
+ }
+ },
+ 'Y4': {
+ axis: 'Y3' // use the same y-axis as series Y3
+ },
+ axes: {
+ y: {
+ valueRange: [40, 70]
+ },
+ y2: {
+ // set axis-related properties here
+ labelsKMB: true
+ }
+ },
+ ylabel: 'Primary y-axis',
+ y2label: 'Secondary y-axis',
+ yAxisLabelWidth: 60
+ }
+ );
+ assertEquals(["40", "45", "50", "55", "60", "65"], getYLabelsForAxis("1"));
+ assertEquals(["900K","1.1M","1.3M","1.5M","1.7M","1.9M"], getYLabelsForAxis("2"));
+
+ g.updateOptions(
+ {
+ axes: {
+ y: {
+ valueRange: [40, 80]
+ },
+ y2: {
+ valueRange: [1e6, 1.2e6]
+ }
+ }
+ }
+ );
+ assertEquals(["40", "45", "50", "55", "60", "65", "70", "75"], getYLabelsForAxis("1"));
+ assertEquals(["1M", "1.02M", "1.05M", "1.08M", "1.1M", "1.13M", "1.15M", "1.18M"], getYLabelsForAxis("2"));
+};
\ No newline at end of file
g.updateOptions({ });
assertEquals([12, 18], g.xAxisRange());
assertEquals([10, 40], g.yAxisRange(0));
+
+ g.updateOptions({valueRange : null, axes: {y:{valueRange : [15, 20]}}});
+ assertEquals([12, 18], g.xAxisRange());
+ assertEquals([15, 20], g.yAxisRange(0));
- g.updateOptions({ dateWindow : null, valueRange : null });
+ g.updateOptions({ dateWindow : null, valueRange : null, axes: null });
assertEquals([10, 20], g.xAxisRange());
assertEquals([0, 55], g.yAxisRange(0));
};
var graph = document.getElementById("graph");
var g = new Dygraph(graph, ZERO_TO_FIFTY, { valueRange: [0,50] });
assertEquals([0, 50], g.yAxisRange(0));
+ g.updateOptions({valueRange: null, axes: {y: {valueRange: [10, 40]}}});
+ assertEquals([10, 40], g.yAxisRange(0));
};
/**
assertEquals(50, g.toDomYCoord(0));
assertEquals(0, g.toDomYCoord(50));
-
+
+ for (var x = 0; x <= 50; x++) {
+ assertEqualsDelta(50 - x, g.toDomYCoord(x), 0.00001);
+ }
+ g.updateOptions({valueRange: null, axes: {y: {valueRange: [0, 50]}}});
+
+ assertEquals(50, g.toDomYCoord(0));
+ assertEquals(0, g.toDomYCoord(50));
+
for (var x = 0; x <= 50; x++) {
assertEqualsDelta(50 - x, g.toDomYCoord(x), 0.00001);
}
lineWidth: 1
});
}
+
+/**
+ * Tests that it is drawing dashes, and it remember the dash history between
+ * points.
+ */
+SimpleDrawingTestCase.prototype.testDrawSimpleDash = function() {
+ var opts = {
+ drawXGrid: false,
+ drawYGrid: false,
+ drawXAxis: false,
+ drawYAxis: false,
+ 'Y1': {strokePattern: [25, 7, 7, 7]},
+ colors: ['#ff0000']
+ };
+
+ var graph = document.getElementById("graph");
+ // Set the dims so we pass if default changes.
+ graph.style.width='480px';
+ graph.style.height='320px';
+ var g = new Dygraph(graph, [[1, 4], [2, 5], [3, 3], [4, 7], [5, 9]], opts);
+ htx = g.hidden_ctx_;
+
+ assertEquals(29, CanvasAssertions.numLinesDrawn(htx, "#ff0000"));
+};
<p>dygraphs has also found use in other organizations:</p>
<ul class='padded-list'>
+ <li><a
+ href="http://iswa.ccmc.gsfc.nasa.gov:8080/IswaSystemWebApp/index.jsp?i_1=388&l_1=99&t_1=316&w_1=800&h_1=400&s_1=0!3!0!ACE.B_x!ACE.B_y!ACE.B_z!">Integrated
+ Space Weather Analysis System</a> (NASA)<br/>
+ <span class="desc">“We use [dygraphs] in the Integrated Space Weather
+ Analysis System available from the Space Weather Laboratory at NASA Goddard
+ Space Flight Center. It works quite well for time series data from various
+ missions and simulations that we store.”</span></li>
+
+
+ <li><a href="http://www.eutelsat.fr">Eutelsat</a><br/>
+ <span class="desc">“Eutelsat uses dygraphs for charting spacecraft
+ telemetry for a fleet of 25 geostationary satellites. The spacecraft
+ engineers are very happy with it. All satellite combined are producing
+ about 200 millions unique data points per day so we really appreciate the
+ excellent performance of dygraphs.”</span></li>
+
+ <li><a href="http://www.10gen.com/mongodb-monitoring-service">10gen MongoDB
+ Monitoring Service</a><br/>
+ <span class="desc">A free monitoring service for MongoDB from 10gen (the
+ creators of MongoDB). Used by thousands of servers and users. Makes use of
+ <a href="tests/synchronize.html">synchronized charts</a> to display many
+ quantities simultaneously.</span></li>
+
<li><a href="http://toolserver.org/~dartar/moodbar/">Wikimedia Foundation - Moodbar data dashboard</a><br/>
<span class="desc">dygraphs is used internally at Wikimedia as a handy solution to monitor the
results of a bunch of small experiments.</span></li>
+ <li><a href="http://code.google.com/p/quadrant-framework/">quadrant-framework</a> (MySQL Load Testing Framework)<br/>
+ <span class="desc">A user friendly framework for creating and visualizing
+ MySQL database load test jobs. For more information on its use of dygraphs,
+ see <a href="http://themattreid.com/wordpress/2011/05/20/quadrant-framework-rev7-update-adds-dygraphs-support/">this post</a>.</span></li>
+
+ <li><a href="http://spinwave.wordpress.com/2011/03/28/spinwave-systems-enables-energy-efficiency-case-studies/">Spinwave Systems</a> (Home energy monitoring)<br/>
+ <span class="desc">dygraphs is used to chart energy usage over time.</span></li>
+
+
<li><a href="http://www.socib.es/jwebchart/?file=http://thredds.socib.es/thredds/dodsC/mooring/weather_station/mobims_calamillor-scb_met001/L1/dep0001_mobims-calamillor_scb-met001_L1_latest.nc">Jwebchart</a><br/>
<span class="desc">
jWebChart is a stand-alone and Thredds' embedded plotting system for
/*global Dygraph:false,RGBColor:false */
"use strict";
+
var DygraphCanvasRenderer = function(dygraph, element, elementContext, layout) {
this.dygraph_ = dygraph;
inner_div.className = 'dygraph-axis-label' +
' dygraph-axis-label-' + axis +
(prec_axis ? ' dygraph-axis-label-' + prec_axis : '');
- inner_div.appendChild(document.createTextNode(txt));
+ inner_div.innerHTML=txt;
div.appendChild(inner_div);
return div;
};
var pointsLength = points.length;
var point, i, j, prevX, prevY, prevYs, color, setName, newYs, err_color, rgb, yscale, axis;
- var setNames = [];
- for (var name in this.layout.datasets) {
- if (this.layout.datasets.hasOwnProperty(name)) {
- setNames.push(name);
- }
- }
+ var setNames = this.layout.setNames;
var setCount = setNames.length;
// TODO(danvk): Move this mapping into Dygraph and get it out of here.
var afterLastIndexInSet = 0;
var setLength = 0;
for (i = 0; i < setCount; i += 1) {
+ firstIndexInSet = this.layout.setPointsOffsets[i];
setLength = this.layout.setPointsLengths[i];
- afterLastIndexInSet += setLength;
+ afterLastIndexInSet = firstIndexInSet + setLength;
setName = setNames[i];
color = this.colors[setName];
var strokeWidth = this.dygraph_.attr_("strokeWidth", setName);
// setup graphics context
+ // TODO(konigsberg): This function has ctx and context. Clarify the difference.
context.save();
var pointSize = this.dygraph_.attr_("pointSize", setName);
prevX = null;
prevY = null;
var drawPoints = this.dygraph_.attr_("drawPoints", setName);
+ var drawPointCallback = this.dygraph_.attr_("drawPointCallback", setName);
+ if (!drawPointCallback) {
+ drawPointCallback = Dygraph.Circles.DEFAULT;
+ }
+ var pointsOnLine = []; // Array of [canvasx, canvasy] pairs.
+ var strokePattern = this.dygraph_.attr_("strokePattern", setName);
+ if (!Dygraph.isArrayLike(strokePattern)) {
+ strokePattern = null;
+ }
for (j = firstIndexInSet; j < afterLastIndexInSet; j++) {
point = points[j];
if (isNullOrNaN(point.canvasy)) {
ctx.beginPath();
ctx.strokeStyle = color;
ctx.lineWidth = this.attr_('strokeWidth');
- ctx.moveTo(prevX, prevY);
- ctx.lineTo(point.canvasx, prevY);
+ this._dashedLine(ctx, prevX, prevY, point.canvasx, prevY, strokePattern);
ctx.stroke();
}
// this will make us move to the next point, not draw a line to it.
ctx.beginPath();
ctx.strokeStyle = color;
ctx.lineWidth = strokeWidth;
- ctx.moveTo(prevX, prevY);
if (stepPlot) {
- ctx.lineTo(point.canvasx, prevY);
+ this._dashedLine(ctx, prevX, prevY, point.canvasx, prevY, strokePattern);
+ prevX = point.canvasx;
}
+ this._dashedLine(ctx, prevX, prevY, point.canvasx, point.canvasy, strokePattern);
prevX = point.canvasx;
prevY = point.canvasy;
- ctx.lineTo(prevX, prevY);
ctx.stroke();
}
}
if (drawPoints || isIsolated) {
- ctx.beginPath();
- ctx.fillStyle = color;
- ctx.arc(point.canvasx, point.canvasy, pointSize,
- 0, 2 * Math.PI, false);
- ctx.fill();
+ pointsOnLine.push([point.canvasx, point.canvasy]);
}
}
}
+ for (var idx = 0; idx < pointsOnLine.length; idx++) {
+ var cb = pointsOnLine[idx];
+ ctx.save();
+ drawPointCallback(
+ this.dygraph_, setName, ctx, cb[0], cb[1], color, pointSize);
+ ctx.restore();
+
+ }
firstIndexInSet = afterLastIndexInSet;
}
context.restore();
};
+
+/**
+ * This does dashed lines onto a canvas for a given pattern. You must call
+ * ctx.stroke() after to actually draw it, much line ctx.lineTo(). It remembers
+ * the state of the line in regards to where we left off on drawing the pattern.
+ * You can draw a dashed line in several function calls and the pattern will be
+ * continous as long as you didn't call this function with a different pattern
+ * in between.
+ * @param ctx The canvas 2d context to draw on.
+ * @param x The start of the line's x coordinate.
+ * @param y The start of the line's y coordinate.
+ * @param x2 The end of the line's x coordinate.
+ * @param y2 The end of the line's y coordinate.
+ * @param pattern The dash pattern to draw, an array of integers where even
+ * index is drawn and odd index is not drawn (Ex. [10, 2, 5, 2], 10 is drawn 5
+ * is drawn, 2 is the space between.). A null pattern, array of length one, or
+ * empty array will do just a solid line.
+ * @private
+ */
+DygraphCanvasRenderer.prototype._dashedLine = function(ctx, x, y, x2, y2, pattern) {
+ // Original version http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas
+ // Modified by Russell Valentine to keep line history and continue the pattern
+ // where it left off.
+ var dx, dy, len, rot, patternIndex, segment;
+
+ // If we don't have a pattern or it is an empty array or of size one just
+ // do a solid line.
+ if (!pattern || pattern.length <= 1) {
+ ctx.moveTo(x, y);
+ ctx.lineTo(x2, y2);
+ return;
+ }
+
+ // If we have a different dash pattern than the last time this was called we
+ // reset our dash history and start the pattern from the begging
+ // regardless of state of the last pattern.
+ if (!Dygraph.compareArrays(pattern, this._dashedLineToHistoryPattern)) {
+ this._dashedLineToHistoryPattern = pattern;
+ this._dashedLineToHistory = [0, 0];
+ }
+ ctx.save();
+
+ // Calculate transformation parameters
+ dx = (x2-x);
+ dy = (y2-y);
+ len = Math.sqrt(dx*dx + dy*dy);
+ rot = Math.atan2(dy, dx);
+
+ // Set transformation
+ ctx.translate(x, y);
+ ctx.moveTo(0, 0);
+ ctx.rotate(rot);
+
+ // Set last pattern index we used for this pattern.
+ patternIndex = this._dashedLineToHistory[0];
+ x = 0;
+ while (len > x) {
+ // Get the length of the pattern segment we are dealing with.
+ segment = pattern[patternIndex];
+ // If our last draw didn't complete the pattern segment all the way we
+ // will try to finish it. Otherwise we will try to do the whole segment.
+ if (this._dashedLineToHistory[1]) {
+ x += this._dashedLineToHistory[1];
+ } else {
+ x += segment;
+ }
+ if (x > len) {
+ // We were unable to complete this pattern index all the way, keep
+ // where we are the history so our next draw continues where we left off
+ // in the pattern.
+ this._dashedLineToHistory = [patternIndex, x-len];
+ x = len;
+ } else {
+ // We completed this patternIndex, we put in the history that we are on
+ // the beginning of the next segment.
+ this._dashedLineToHistory = [(patternIndex+1)%pattern.length, 0];
+ }
+
+ // We do a line on a even pattern index and just move on a odd pattern index.
+ // The move is the empty space in the dash.
+ if(patternIndex % 2 === 0) {
+ ctx.lineTo(x, 0);
+ } else {
+ ctx.moveTo(x, 0);
+ }
+ // If we are not done, next loop process the next pattern segment, or the
+ // first segment again if we are at the end of the pattern.
+ patternIndex = (patternIndex+1) % pattern.length;
+ }
+ ctx.restore();
+};
if (row < 0) return selection;
- var col = 1;
var datasets = this.date_graph.layout_.datasets;
- for (var k in datasets) {
- if (!datasets.hasOwnProperty(k)) continue;
- selection.push({row: row, column: col});
- col++;
+ for (var setIdx = 0; setIdx < datasets.length; ++setIdx) {
+ selection.push({row: row, column: setIdx + 1});
}
return selection;
};
-
}
}
};
+
+// Default interaction model when using the range selector.
+Dygraph.Interaction.dragIsPanInteractionModel = {
+ mousedown: function(event, g, context) {
+ context.initializeMouseDown(event, g, context);
+ Dygraph.startPan(event, g, context);
+ },
+ mousemove: function(event, g, context) {
+ if (context.isPanning) {
+ Dygraph.movePan(event, g, context);
+ }
+ },
+ mouseup: function(event, g, context) {
+ if (context.isPanning) {
+ Dygraph.endPan(event, g, context);
+ }
+ }
+};
var DygraphLayout = function(dygraph) {
this.dygraph_ = dygraph;
this.datasets = [];
+ this.setNames = [];
this.annotations = [];
this.yAxes_ = null;
};
DygraphLayout.prototype.addDataset = function(setname, set_xy) {
- this.datasets[setname] = set_xy;
+ this.datasets.push(set_xy);
+ this.setNames.push(setname);
};
DygraphLayout.prototype.getPlotArea = function() {
this.minxval = this.dateWindow_[0];
this.maxxval = this.dateWindow_[1];
} else {
- for (var name in this.datasets) {
- if (!this.datasets.hasOwnProperty(name)) continue;
- var series = this.datasets[name];
+ for (var setIdx = 0; setIdx < this.datasets.length; ++setIdx) {
+ var series = this.datasets[setIdx];
if (series.length > 1) {
var x1 = series[0][0];
if (!this.minxval || x1 < this.minxval) this.minxval = x1;
// for every data set since the points are added in order of the sets in
// datasets.
this.setPointsLengths = [];
+ this.setPointsOffsets = [];
- for (var setName in this.datasets) {
- if (!this.datasets.hasOwnProperty(setName)) continue;
-
- var dataset = this.datasets[setName];
+ for (var setIdx = 0; setIdx < this.datasets.length; ++setIdx) {
+ var dataset = this.datasets[setIdx];
+ var setName = this.setNames[setIdx];
var axis = this.dygraph_.axisPropertiesForSeries(setName);
+ this.setPointsOffsets.push(this.points.length);
var setPointsLength = 0;
for (var j = 0; j < dataset.length; j++) {
// Copy over the error terms
var i = 0; // index in this.points
- for (var setName in this.datasets) {
- if (!this.datasets.hasOwnProperty(setName)) continue;
+ for (var setIdx = 0; setIdx < this.datasets.length; ++setIdx) {
var j = 0;
- var dataset = this.datasets[setName];
+ var dataset = this.datasets[setIdx];
+ var setName = this.setNames[setIdx];
var axis = this.dygraph_.axisPropertiesForSeries(setName);
for (j = 0; j < dataset.length; j++, i++) {
var item = dataset[j];
*/
DygraphLayout.prototype.removeAllDatasets = function() {
delete this.datasets;
+ delete this.setNames;
+ delete this.setPointsLengths;
+ delete this.setPointsOffsets;
this.datasets = [];
+ this.setNames = [];
+ this.setPointsLengths = [];
+ this.setPointsOffsets = [];
};
/**
"default": "false",
"labels": ["Data Line display"],
"type": "boolean",
- "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."
+ "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. The small dot can be \
+ replaced with a custom rendering by supplying a \
+ <a href='#drawPointCallback'>drawPointCallback</a>."
+ },
+ "drawPointCallback": {
+ "default": "null",
+ "labels": ["Data Line display"],
+ "type": "function(g, seriesName, canvasContext, cx, cy, color, pointSize)",
+ "parameters": [
+ [ "g" , "the reference graph" ],
+ [ "seriesName" , "the name of the series" ],
+ [ "canvasContext" , "the canvas to draw on" ],
+ [ "cx" , "center x coordinate" ],
+ [ "cy" , "center y coordinate" ],
+ [ "color" , "series color" ],
+ [ "pointSize" , "the radius of the image." ]
+ ],
+ "description": "Draw a custom item when drawPoints is enabled. \
+ Default is a small dot matching the series color. This method \
+ should constrain drawing to within pointSize pixels from (cx, cy). \
+ Also see <a href='#drawHighlightPointCallback'>drawHighlightPointCallback</a>"
},
"height": {
"default": "320",
"labels": ["Overall display"],
"type": "integer",
- "description": "Height, in pixels, of the chart. If the container div has been explicitly sized, this will be ignored."
+ "description": "Height, in pixels, of the chart. If the container div has \
+ been explicitly sized, this will be ignored."
},
"zoomCallback": {
"default": "null",
"labels": ["Callbacks"],
"type": "function(minDate, maxDate, yRanges)",
- "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."
+ "parameters": [
+ [ "minDate" , "milliseconds since epoch" ],
+ [ "maxDate" , "milliseconds since epoch." ],
+ [ "yRanges" , "is an array of [bottom, top] pairs, one for each y-axis." ]
+ ],
+ "description": "A function to call when the zoom window is changed (either \
+ by zooming in or out)."
},
"pointClickCallback": {
"snippet": "function(e, point){<br> alert(point);<br>}",
"default": "null",
"labels": ["Callbacks", "Interactive Elements"],
"type": "function(e, point)",
- "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 point that was clicked. The 'point' argument has these properties:\n * xval/yval: The data coordinates of the point (with dates/times as millis since epoch) \n * canvasx/canvasy: The canvas coordinates at which the point is drawn. \n name: The name of the data series to which the point belongs"
+ "parameters": [
+ [ "e" , "the event object for the click" ],
+ [ "point" , "the point that was clicked See <a href='#point_properties'>Point properties</a> for details" ]
+ ],
+ "description": "A function to call when a data point is clicked. and the point that was clicked."
},
"colors": {
"default": "(see description)",
"highlightCallback": {
"default": "null",
"labels": ["Callbacks"],
- "type": "function(event, x, points,row)",
- "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>"
+ "type": "function(event, x, points, row)",
+ "description": "When set, this callback gets called every time a new point is highlighted.",
+ "parameters": [
+ ["event", "the JavaScript mousemove event"],
+ ["x", "the x-coordinate of the highlighted points"],
+ ["points", "an array of highlighted points: <code>[ {name: 'series', yval: y-value}, … ]</code>"],
+ ["row", "???"]
+ ]
+ },
+ "drawHighlightPointCallback": {
+ "default": "null",
+ "labels": ["Data Line display"],
+ "type": "function(g, seriesName, canvasContext, cx, cy, color, pointSize)",
+ "parameters": [
+ [ "g" , "the reference graph" ],
+ [ "seriesName" , "the name of the series" ],
+ [ "canvasContext" , "the canvas to draw on" ],
+ [ "cx" , "center x coordinate" ],
+ [ "cy" , "center y coordinate" ],
+ [ "color" , "series color" ],
+ [ "pointSize" , "the radius of the image." ]
+ ],
+ "description": "Draw a custom item when a point is highlighted. \
+ Default is a small dot matching the series color. This method \
+ should constrain drawing to within pointSize pixels from (cx, cy) \
+ Also see <a href='#drawPointCallback'>drawPointCallback</a>"
},
"includeZero": {
"default": "false",
"default": "null",
"labels": ["Callbacks"],
"type": "function(event)",
- "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."
+ "parameters": [
+ [ "event" , "the mouse event" ]
+ ],
+ "description": "When set, this callback gets called every time the user stops highlighting any point by mousing out of the graph."
},
"axisTickSize": {
"default": "3.0",
"default": "null",
"labels": ["Annotations"],
"type": "function(annotation, point, dygraph, event)",
+ "parameters": [
+ [ "annotation" , "the annotation left" ],
+ [ "point" , "the point associated with the annotation" ],
+ [ "dygraph" , "the reference graph" ],
+ [ "event" , "the mouse event" ]
+ ],
"description": "If provided, this function is called whenever the user mouses out of an annotation."
},
"annotationClickHandler": {
"default": "null",
"labels": ["Annotations"],
"type": "function(annotation, point, dygraph, event)",
+ "parameters": [
+ [ "annotation" , "the annotation left" ],
+ [ "point" , "the point associated with the annotation" ],
+ [ "dygraph" , "the reference graph" ],
+ [ "event" , "the mouse event" ]
+ ],
"description": "If provided, this function is called whenever the user clicks on an annotation."
},
"annotationDblClickHandler": {
"default": "null",
"labels": ["Annotations"],
"type": "function(annotation, point, dygraph, event)",
+ "parameters": [
+ [ "annotation" , "the annotation left" ],
+ [ "point" , "the point associated with the annotation" ],
+ [ "dygraph" , "the reference graph" ],
+ [ "event" , "the mouse event" ]
+ ],
"description": "If provided, this function is called whenever the user double-clicks on an annotation."
},
"drawCallback": {
"default": "null",
"labels": ["Callbacks"],
"type": "function(dygraph, is_initial)",
- "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."
+ "parameters": [
+ [ "dygraph" , "The graph being drawn" ],
+ [ "is_initial" , "True if this is the initial draw, false for subsequent draws." ]
+ ],
+ "description": "When set, this callback gets called every time the dygraph is drawn. This includes the initial draw, after zooming and repeatedly while panning."
},
"labelsKMG2": {
"default": "false",
"default": "null",
"labels": ["Callbacks"],
"type": "function(canvas, area, dygraph)",
+ "parameters": [
+ [ "canvas" , "the canvas to draw on" ],
+ [ "area" , "" ],
+ [ "dygraph" , "the reference graph" ]
+ ],
"description": "When set, this callback gets called before the chart is drawn. It details on how to use this."
},
"width": {
"default": "Dygraph.dateTicker or Dygraph.numericTicks",
"labels": ["Axis display"],
"type": "function(min, max, pixels, opts, dygraph, vals) -> [{v: ..., label: ...}, ...]",
+ "parameters": [
+ [ "min" , "" ],
+ [ "max" , "" ],
+ [ "pixels" , "" ],
+ [ "opts" , "" ],
+ [ "dygraph" , "the reference graph" ],
+ [ "vals" , "" ]
+ ],
"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. See dygraph-tickers.js for an extensive discussion. This is set on a <a href='per-axis.html'>per-axis</a> basis."
},
"xAxisLabelWidth": {
"example": "0.5, 2.0",
"description": "The width of the lines connecting data points. This can be used to increase the contrast or some graphs."
},
+ "strokePattern": {
+ "default": "null",
+ "labels": ["Data Line display"],
+ "type": "array<integer>",
+ "example": "[10, 2, 5, 2]",
+ "description": "A custom pattern array where the even index is a draw and odd is a space in pixels. If null then it draws a solid line. The array should have a even length as any odd lengthed array could be expressed as a smaller even length array."
+ },
"wilsonInterval": {
"default": "true",
"labels": ["Error Bars"],
"labels": ["Axis display"],
"type": "Array of two numbers",
"example": "[10, 110]",
- "description": "Explicitly set the vertical range of the graph to [low, high]."
+ "description": "Explicitly set the vertical range of the graph to [low, high]. This may be set on a per-axis basis to define each y-axis separately."
},
"labelsDivWidth": {
"default": "250",
"default": "Depends on the data type",
"labels": ["Axis display"],
"type": "function(number or Date, granularity, opts, dygraph)",
- "description": "Function to call to format the tick values that appear along an axis. This is usually set on a <a href='per-axis.html'>per-axis</a> basis. The first parameter is either a number (for a numeric axis) or a Date object (for a date axis). The second argument specifies how fine-grained the axis is. For date axes, this is a reference to the time granularity enumeration, defined in dygraph-tickers.js, e.g. Dygraph.WEEKLY. opts is a function which provides access to various options on the dygraph, e.g. opts('labelsKMB')."
+ "parameters": [
+ [ "number or date" , "Either a number (for a numeric axis) or a Date object (for a date axis)" ],
+ [ "granularity" , "specifies how fine-grained the axis is. For date axes, this is a reference to the time granularity enumeration, defined in dygraph-tickers.js, e.g. Dygraph.WEEKLY." ],
+ [ "opts" , "a function which provides access to various options on the dygraph, e.g. opts('labelsKMB')." ],
+ [ "dygraph" , "the referenced graph" ]
+ ],
+ "description": "Function to call to format the tick values that appear along an axis. This is usually set on a <a href='per-axis.html'>per-axis</a> basis."
},
"clickCallback": {
"snippet": "function(e, date_millis){<br> alert(new Date(date_millis));<br>}",
"default": "null",
"labels": ["Callbacks"],
"type": "function(e, x, points)",
- "description": "A function to call when the canvas is clicked. The function should take three arguments, the event object for the click, the x-value that was clicked (for dates this is millis since epoch), and the closest points along that date. The points have these properties:\n * xval/yval: The data coordinates of the point (with dates/times as millis since epoch) \n * canvasx/canvasy: The canvas coordinates at which the point is drawn. \n name: The name of the data series to which the point belongs"
+ "parameters": [
+ [ "e" , "The event object for the click" ],
+ [ "x" , "The x value that was clicked (for dates, this is milliseconds since epoch)" ],
+ [ "points" , "The closest points along that date. See <a href='#point_properties'>Point properties</a> for details." ]
+ ],
+ "description": "A function to call when the canvas is clicked."
},
"yAxisLabelFormatter": {
"default": "",
return e.srcElement == self.iePanOverlay_;
} else {
// Getting clientX directly from the event is not accurate enough :(
- var clientX = self.canvasRect_.x + (e.layerX !== undefined ? e.layerX : e.offsetX);
+ var clientX;
+ if (e.offsetX != undefined) {
+ clientX = self.canvasRect_.x + e.offsetX;
+ } else {
+ clientX = e.clientX;
+ }
var zoomHandleStatus = self.getZoomHandleStatus_();
return (clientX > zoomHandleStatus.leftHandlePos && clientX < zoomHandleStatus.rightHandlePos);
}
}
};
- var interactionModel = {
- mousedown: function(event, g, context) {
- context.initializeMouseDown(event, g, context);
- Dygraph.startPan(event, g, context);
- },
- mousemove: function(event, g, context) {
- if (context.isPanning) {
- Dygraph.movePan(event, g, context);
- }
- },
- mouseup: function(event, g, context) {
- if (context.isPanning) {
- Dygraph.endPan(event, g, context);
- }
- }
- };
-
- this.dygraph_.attrs_.interactionModel = interactionModel;
+ this.dygraph_.attrs_.interactionModel =
+ Dygraph.Interaction.dragIsPanInteractionModel;
this.dygraph_.attrs_.panEdgeFraction = 0.0001;
var dragStartEvent = window.opera ? 'mousedown' : 'dragstart';
// https://github.com/eriwen/javascript-stacktrace
Dygraph.LOG_STACK_TRACES = false;
+/** A dotted line stroke pattern. */
+Dygraph.DOTTED_LINE = [2, 2];
+/** A dashed line stroke pattern. */
+Dygraph.DASHED_LINE = [7, 3];
+/** A dot dash stroke pattern. */
+Dygraph.DOT_DASH_LINE = [7, 2, 2, 2];
+
/**
* @private
* Log an error on the JS console at the given severity.
'clickCallback': true,
'digitsAfterDecimal': true,
'drawCallback': true,
+ 'drawHighlightPointCallback': true,
'drawPoints': true,
+ 'drawPointCallback': true,
'drawXGrid': true,
'drawYGrid': true,
'fillAlpha': true,
return requiresNewPoints;
};
+
+/**
+ * Compares two arrays to see if they are equal. If either parameter is not an
+ * array it will return false. Does a shallow compare
+ * Dygraph.compareArrays([[1,2], [3, 4]], [[1,2], [3,4]]) === false.
+ * @param array1 first array
+ * @param array2 second array
+ * @return True if both parameters are arrays, and contents are equal.
+ */
+Dygraph.compareArrays = function(array1, array2) {
+ if (!Dygraph.isArrayLike(array1) || !Dygraph.isArrayLike(array2)) {
+ return false;
+ }
+ if (array1.length !== array2.length) {
+ return false;
+ }
+ for (var i = 0; i < array1.length; i++) {
+ if (array1[i] !== array2[i]) {
+ return false;
+ }
+ }
+ return true;
+};
+
+/**
+ * ctx: the canvas context
+ * sides: the number of sides in the shape.
+ * radius: the radius of the image.
+ * cx: center x coordate
+ * cy: center y coordinate
+ * rotationRadians: the shift of the initial angle, in radians.
+ * delta: the angle shift for each line. If missing, creates a regular
+ * polygon.
+ */
+Dygraph.regularShape_ = function(
+ ctx, sides, radius, cx, cy, rotationRadians, delta) {
+ rotationRadians = rotationRadians ? rotationRadians : 0;
+ delta = delta ? delta : Math.PI * 2 / sides;
+
+ ctx.beginPath();
+ var first = true;
+ var initialAngle = rotationRadians;
+ var angle = initialAngle;
+
+ var computeCoordinates = function() {
+ var x = cx + (Math.sin(angle) * radius);
+ var y = cy + (-Math.cos(angle) * radius);
+ return [x, y];
+ };
+
+ var initialCoordinates = computeCoordinates();
+ var x = initialCoordinates[0];
+ var y = initialCoordinates[1];
+ ctx.moveTo(x, y);
+
+ for (var idx = 0; idx < sides; idx++) {
+ angle = (idx == sides - 1) ? initialAngle : (angle + delta);
+ var coords = computeCoordinates();
+ ctx.lineTo(coords[0], coords[1]);
+ }
+ ctx.stroke();
+ ctx.closePath();
+}
+
+Dygraph.shapeFunction_ = function(sides, rotationRadians, delta) {
+ return function(g, name, ctx, cx, cy, color, radius) {
+ ctx.lineWidth = 1;
+ ctx.strokeStyle = color;
+ Dygraph.regularShape_(ctx, sides, radius, cx, cy, rotationRadians, delta);
+ };
+};
+
+Dygraph.DrawPolygon_ = function(sides, rotationRadians, ctx, cx, cy, color, radius, delta) {
+ new Dygraph.RegularShape_(sides, rotationRadians, delta).draw(ctx, cx, cy, radius);
+}
+
+Dygraph.Circles = {
+ DEFAULT : function(g, name, ctx, canvasx, canvasy, color, radius) {
+ ctx.beginPath();
+ ctx.fillStyle = color;
+ ctx.arc(canvasx, canvasy, radius, 0, 2 * Math.PI, false);
+ ctx.fill();
+ },
+ TRIANGLE : Dygraph.shapeFunction_(3),
+ SQUARE : Dygraph.shapeFunction_(4, Math.PI / 4),
+ DIAMOND : Dygraph.shapeFunction_(4),
+ PENTAGON : Dygraph.shapeFunction_(5),
+ HEXAGON : Dygraph.shapeFunction_(6),
+ CIRCLE : function(g, name, ctx, cx, cy, color, radius) {
+ ctx.beginPath();
+ ctx.strokeStyle = color;
+ ctx.arc(cx, cy, radius, 0, 2 * Math.PI, false);
+ ctx.stroke();
+ },
+ STAR : Dygraph.shapeFunction_(5, 0, 4 * Math.PI / 5),
+ PLUS : function(g, name, ctx, cx, cy, color, radius) {
+ ctx.lineWidth = 1;
+ ctx.strokeStyle = color;
+
+ ctx.beginPath();
+ ctx.moveTo(cx + radius, cy);
+ ctx.lineTo(cx - radius, cy);
+ ctx.closePath();
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.moveTo(cx, cy + radius);
+ ctx.lineTo(cx, cy - radius);
+ ctx.closePath();
+
+ ctx.stroke();
+ },
+ EX : function(g, name, ctx, cx, cy, color, radius) {
+ ctx.lineWidth = 1;
+ ctx.strokeStyle = "black";
+
+ ctx.beginPath();
+ ctx.moveTo(cx + radius, cy + radius);
+ ctx.lineTo(cx - radius, cy - radius);
+ ctx.closePath();
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.moveTo(cx + radius, cy - radius);
+ ctx.lineTo(cx - radius, cy + radius);
+ ctx.closePath();
+
+ ctx.stroke();
+ }
+};
Dygraph.updateDeep(this.attrs_, Dygraph.DEFAULT_ATTRS);
this.boundaryIds_ = [];
+ this.setIndexByName_ = {};
// Create the containing DIV and other interactive elements
this.createInterface_();
Dygraph.OPTIONS_REFERENCE[name] = true;
}
// </REMOVE_FOR_COMBINED>
- if (seriesName &&
+ if (this.user_attrs_ !== null && seriesName &&
typeof(this.user_attrs_[seriesName]) != 'undefined' &&
this.user_attrs_[seriesName] !== null &&
typeof(this.user_attrs_[seriesName][name]) != 'undefined') {
return this.user_attrs_[seriesName][name];
- } else if (typeof(this.user_attrs_[name]) != 'undefined') {
+ } else if (this.user_attrs_ !== null && typeof(this.user_attrs_[name]) != 'undefined') {
return this.user_attrs_[name];
- } else if (typeof(this.attrs_[name]) != 'undefined') {
+ } else if (this.attrs_ !== null && typeof(this.attrs_[name]) != 'undefined') {
return this.attrs_[name];
} else {
return null;
}
var dygraph = this;
- Dygraph.addEvent(this.mouseEventElement_, 'mousemove', function(e) {
- dygraph.mouseMove_(e);
- });
- Dygraph.addEvent(this.mouseEventElement_, 'mouseout', function(e) {
- dygraph.mouseOut_(e);
- });
+
+ this.mouseMoveHandler = function(e) {
+ dygraph.mouseMove_(e);
+ };
+ Dygraph.addEvent(this.mouseEventElement_, 'mousemove', this.mouseMoveHandler);
+
+ this.mouseOutHandler = function(e) {
+ dygraph.mouseOut_(e);
+ };
+ Dygraph.addEvent(this.mouseEventElement_, 'mouseout', this.mouseOutHandler);
this.createStatusMessage_();
this.createDragInterface_();
+ this.resizeHandler = function(e) {
+ dygraph.resize();
+ };
+
// Update when the window is resized.
// TODO(danvk): drop frames depending on complexity of the chart.
- Dygraph.addEvent(window, 'resize', function(e) {
- dygraph.resize();
- });
+ Dygraph.addEvent(window, 'resize', this.resizeHandler);
};
/**
node.removeChild(node.firstChild);
}
};
+
+ // remove mouse event handlers
+ Dygraph.removeEvent(this.mouseEventElement_, 'mouseout', this.mouseOutHandler);
+ Dygraph.removeEvent(this.mouseEventElement_, 'mousemove', this.mouseMoveHandler);
removeRecursive(this.maindiv_);
var nullOut = function(obj) {
}
}
};
-
+ // remove event handlers
+ Dygraph.removeEvent(window,'resize',this.resizeHandler);
+ this.resizeHandler = null;
// These may not all be necessary, but it can't hurt...
nullOut(this.layout_);
nullOut(this.plotter_);
}
for (var i = 0; i < this.axes_.length; i++) {
- if (this.axes_[i].valueWindow !== null) {
+ if (typeof(this.axes_[i].valueWindow) !== 'undefined' && this.axes_[i].valueWindow !== null) {
dirty = true;
dirtyY = true;
}
newValueRanges = [];
for (i = 0; i < this.axes_.length; i++) {
- newValueRanges.push(this.axes_[i].extremeRange);
+ var axis = this.axes_[i];
+ newValueRanges.push(axis.valueRange != null ? axis.valueRange : axis.extremeRange);
}
}
Dygraph.prototype.idxToRow_ = function(idx) {
if (idx < 0) return -1;
- for (var i in this.layout_.datasets) {
- if (idx < this.layout_.datasets[i].length) {
- return this.boundaryIds_[0][0]+idx;
+ // make sure that you get the boundaryIds record which is also defined (see bug #236)
+ var boundaryIdx = -1;
+ for (var i = 0; i < this.boundaryIds_.length; i++) {
+ if (this.boundaryIds_[i] !== undefined) {
+ boundaryIdx = i;
+ break;
+ }
+ }
+ if (boundaryIdx < 0) return -1;
+ for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
+ var set = this.layout_.datasets[setIdx];
+ if (idx < set.length) {
+ return this.boundaryIds_[boundaryIdx][0] + idx;
}
- idx -= this.layout_.datasets[i].length;
+ idx -= set.length;
}
return -1;
};
/**
* @private
+ * Generates legend html dash for any stroke pattern. It will try to scale the
+ * pattern to fit in 1em width. Or if small enough repeat the partern for 1em
+ * width.
+ * @param strokePattern The pattern
+ * @param color The color of the series.
+ * @param oneEmWidth The width in pixels of 1em in the legend.
+ */
+Dygraph.prototype.generateLegendDashHTML_ = function(strokePattern, color, oneEmWidth) {
+ var dash = "";
+ var i, j, paddingLeft, marginRight;
+ var strokePixelLength = 0, segmentLoop = 0;
+ var normalizedPattern = [];
+ var loop;
+ // IE 7,8 fail at these divs, so they get boring legend, have not tested 9.
+ var isIE = (/MSIE/.test(navigator.userAgent) && !window.opera);
+ if(isIE) {
+ return "—";
+ }
+ if (!strokePattern || strokePattern.length <= 1) {
+ // Solid line
+ dash = "<div style=\"display: inline-block; position: relative; " +
+ "bottom: .5ex; padding-left: 1em; height: 1px; " +
+ "border-bottom: 2px solid " + color + ";\"></div>";
+ } else {
+ // Compute the length of the pixels including the first segment twice,
+ // since we repeat it.
+ for (i = 0; i <= strokePattern.length; i++) {
+ strokePixelLength += strokePattern[i%strokePattern.length];
+ }
+
+ // See if we can loop the pattern by itself at least twice.
+ loop = Math.floor(oneEmWidth/(strokePixelLength-strokePattern[0]));
+ if (loop > 1) {
+ // This pattern fits at least two times, no scaling just convert to em;
+ for (i = 0; i < strokePattern.length; i++) {
+ normalizedPattern[i] = strokePattern[i]/oneEmWidth;
+ }
+ // Since we are repeating the pattern, we don't worry about repeating the
+ // first segment in one draw.
+ segmentLoop = normalizedPattern.length;
+ } else {
+ // If the pattern doesn't fit in the legend we scale it to fit.
+ loop = 1;
+ for (i = 0; i < strokePattern.length; i++) {
+ normalizedPattern[i] = strokePattern[i]/strokePixelLength;
+ }
+ // For the scaled patterns we do redraw the first segment.
+ segmentLoop = normalizedPattern.length+1;
+ }
+ // Now make the pattern.
+ for (j = 0; j < loop; j++) {
+ for (i = 0; i < segmentLoop; i+=2) {
+ // The padding is the drawn segment.
+ paddingLeft = normalizedPattern[i%normalizedPattern.length];
+ if (i < strokePattern.length) {
+ // The margin is the space segment.
+ marginRight = normalizedPattern[(i+1)%normalizedPattern.length];
+ } else {
+ // The repeated first segment has no right margin.
+ marginRight = 0;
+ }
+ dash += "<div style=\"display: inline-block; position: relative; " +
+ "bottom: .5ex; margin-right: " + marginRight + "em; padding-left: " +
+ paddingLeft + "em; height: 1px; border-bottom: 2px solid " + color +
+ ";\"></div>";
+ }
+ }
+ }
+ return dash;
+};
+
+/**
+ * @private
* Generates HTML for the legend which is displayed when hovering over the
* chart. If no selected points are specified, a default legend is returned
* (this may just be the empty string).
* @param { Number } [x] The x-value of the selected points.
* @param { [Object] } [sel_points] List of selected points for the given
* x-value. Should have properties like 'name', 'yval' and 'canvasy'.
+ * @param { Number } [oneEmWidth] The pixel width for 1em in the legend.
*/
-Dygraph.prototype.generateLegendHTML_ = function(x, sel_points) {
+Dygraph.prototype.generateLegendHTML_ = function(x, sel_points, oneEmWidth) {
// If no points are selected, we display a default legend. Traditionally,
// this has been blank. But a better default would be a conventional legend,
// which provides essential information for a non-interactive chart.
- var html, sepLines, i, c;
+ var html, sepLines, i, c, dash, strokePattern;
if (typeof(x) === 'undefined') {
if (this.attr_('legend') != 'always') return '';
if (!this.visibility()[i - 1]) continue;
c = this.plotter_.colors[labels[i]];
if (html !== '') html += (sepLines ? '<br/>' : ' ');
- html += "<b><span style='color: " + c + ";'>—" + labels[i] +
- "</span></b>";
+ strokePattern = this.attr_("strokePattern", labels[i]);
+ dash = this.generateLegendDashHTML_(strokePattern, c, oneEmWidth);
+ html += "<span style='font-weight: bold; color: " + c + ";'>" + dash +
+ " " + labels[i] + "</span>";
}
return html;
}
* x-value. Should have properties like 'name', 'yval' and 'canvasy'.
*/
Dygraph.prototype.setLegendHTML_ = function(x, sel_points) {
- var html = this.generateLegendHTML_(x, sel_points);
var labelsDiv = this.attr_("labelsDiv");
+ var sizeSpan = document.createElement('span');
+ // Calculates the width of 1em in pixels for the legend.
+ sizeSpan.setAttribute('style', 'margin: 0; padding: 0 0 0 1em; border: 0;');
+ labelsDiv.appendChild(sizeSpan);
+ var oneEmWidth=sizeSpan.offsetWidth;
+
+ var html = this.generateLegendHTML_(x, sel_points, oneEmWidth);
if (labelsDiv !== null) {
labelsDiv.innerHTML = html;
} else {
if (!Dygraph.isOK(pt.canvasy)) continue;
var circleSize = this.attr_('highlightCircleSize', pt.name);
- ctx.beginPath();
- ctx.fillStyle = this.plotter_.colors[pt.name];
- ctx.arc(canvasx, pt.canvasy, circleSize, 0, 2 * Math.PI, false);
- ctx.fill();
+ var callback = this.attr_("drawHighlightPointCallback", pt.name);
+ if (!callback) {
+ callback = Dygraph.Circles.DEFAULT;
+ }
+ callback(this.g, pt.name, ctx, canvasx, pt.canvasy,
+ this.plotter_.colors[pt.name], circleSize);
}
ctx.restore();
}
if (row !== false && row >= 0) {
- for (var i in this.layout_.datasets) {
- if (row < this.layout_.datasets[i].length) {
+ for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
+ var set = this.layout_.datasets[setIdx];
+ if (row < set.length) {
var point = this.layout_.points[pos+row];
if (this.attr_("stackedGraph")) {
this.selPoints_.push(point);
}
- pos += this.layout_.datasets[i].length;
+ pos += set.length;
}
}
var extremes = packed[1];
this.boundaryIds_ = packed[2];
+ this.setIndexByName_ = {};
+ var labels = this.attr_("labels");
+ if (labels.length > 0) {
+ this.setIndexByName_[labels[0]] = 0;
+ }
for (var i = 1; i < datasets.length; i++) {
+ this.setIndexByName_[labels[i]] = i;
if (!this.visibility()[i - 1]) continue;
- this.layout_.addDataset(this.attr_("labels")[i], datasets[i]);
+ this.layout_.addDataset(labels[i], datasets[i]);
}
this.computeYAxisRanges_(extremes);
Dygraph.prototype.computeYAxes_ = function() {
// Preserve valueWindow settings if they exist, and if the user hasn't
// specified a new valueRange.
- var i, valueWindows, seriesName, axis, index;
+ var i, valueWindows, seriesName, axis, index, opts, v;
if (this.axes_ !== undefined && this.user_attrs_.hasOwnProperty("valueRange") === false) {
valueWindows = [];
for (index = 0; index < this.axes_.length; index++) {
// Copy global axis options over to the first axis.
for (i = 0; i < axisOptions.length; i++) {
var k = axisOptions[i];
- var v = this.attr_(k);
+ v = this.attr_(k);
if (v) this.axes_[0][k] = v;
}
}
if (typeof(axis) == 'object') {
// Add a new axis, making a copy of its per-axis options.
- var opts = {};
+ opts = {};
Dygraph.update(opts, this.axes_[0]);
Dygraph.update(opts, { valueRange: null }); // shouldn't inherit this.
var yAxisId = this.axes_.length;
this.axes_[index].valueWindow = valueWindows[index];
}
}
+
+ // New axes options
+ for (axis = 0; axis < this.axes_.length; axis++) {
+ if (axis === 0) {
+ opts = this.optionsViewForAxis_('y' + (axis ? '2' : ''));
+ v = opts("valueRange");
+ if (v) this.axes_[axis].valueRange = v;
+ } else { // To keep old behavior
+ var axes = this.user_attrs_.axes;
+ if (axes && axes.y2) {
+ v = axes.y2.valueRange;
+ if (v) this.axes_[axis].valueRange = v;
+ }
+ }
+ }
+
};
/**
* @private
*/
Dygraph.prototype.parseDataTable_ = function(data) {
+ var shortTextForAnnotationNum = function(num) {
+ // converts [0-9]+ [A-Z][a-z]*
+ // example: 0=A, 1=B, 25=Z, 26=Aa, 27=Ab
+ // and continues like.. Ba Bb .. Za .. Zz..Aaa...Zzz Aaaa Zzzz
+ var shortText = String.fromCharCode(65 /* A */ + num % 26);
+ num = Math.floor(num / 26);
+ while ( num > 0 ) {
+ shortText = String.fromCharCode(65 /* A */ + (num - 1) % 26 ) + shortText.toLowerCase();
+ num = Math.floor((num - 1) / 26);
+ }
+ return shortText;
+ }
+
var cols = data.getNumberOfColumns();
var rows = data.getNumberOfRows();
var ann = {};
ann.series = data.getColumnLabel(col);
ann.xval = row[0];
- ann.shortText = String.fromCharCode(65 /* A */ + annotations.length);
+ ann.shortText = shortTextForAnnotationNum(annotations.length);
ann.text = '';
for (var k = 0; k < annotationCols[col].length; k++) {
if (k) ann.text += "\n";
};
/**
+ * Get the list of label names for this graph. The first column is the
+ * x-axis, so the data series names start at index 1.
+ */
+Dygraph.prototype.getLabels = function(name) {
+ return this.attr_("labels").slice();
+};
+
+/**
* 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.
*/
Dygraph.prototype.indexFromSetName = function(name) {
- var labels = this.attr_("labels");
- for (var i = 0; i < labels.length; i++) {
- if (labels[i] == name) return i;
- }
- return null;
+ return this.setIndexByName_[name];
};
/**
function dblClickV3(event, g, context) {
// Reducing by 20% makes it 80% the original size, which means
// to restore to original size it must grow by 25%
+
+ if (!(event.offsetX && event.offsetY)){
+ event.offsetX = event.layerX - event.target.offsetLeft;
+ event.offsetY = event.layerY - event.target.offsetTop;
+ }
+
var percentages = offsetToPercentage(g, event.offsetX, event.offsetY);
var xPct = percentages[0];
var yPct = percentages[1];
// that verbatim, it would be a 7.5%.
var percentage = normal / 50;
+ if (!(event.offsetX && event.offsetY)){
+ event.offsetX = event.layerX - event.target.offsetLeft;
+ event.offsetY = event.layerY - event.target.offsetTop;
+ }
+
var percentages = offsetToPercentage(g, event.offsetX, event.offsetY);
var xPct = percentages[0];
var yPct = percentages[1];
elif '</JSON>' in line:
in_json = False
elif in_json:
+ if line.endswith("\\\n"): # hacked in line continuation support with trailing \.
+ line = line[:-2]
js += line
# TODO(danvk): better errors here.
if label not in labels:
labels.append(label)
-print """
+print """<!DOCTYPE HTML>
<html>
<head>
<title>Dygraphs Options Reference</title>
p.option {
padding-left: 25px;
}
+ div.parameters {
+ padding-left: 15px;
+ }
#nav {
position: fixed;
}
"""
print """
-<div id=nav>
+<div id='nav'>
<h2>Dygraphs</h2>
<ul>
<li><a href="index.html">Home</a>
return f.replace('tests/', '').replace('.html', '')
print """
-<div id=content>
+<div id='content'>
<h2>Options Reference</h2>
<p>Dygraphs tries to do a good job of displaying your data without any further configuration. But inevitably, you're going to want to tinker. Dygraphs provides a rich set of options for configuring its display and behavior.</p>
-<a name="usage"><h3>Usage</h3>
-<p>You specify options in the third parameter to the dygraphs constructor:
+<a name="usage"></a><h3>Usage</h3>
+<p>You specify options in the third parameter to the dygraphs constructor:</p>
<pre>g = new Dygraph(div,
data,
{
});
</pre>
-After you've created a Dygraph, you can change an option by calling the <code>updateOptions</code> method:
+<p>After you've created a Dygraph, you can change an option by calling the <code>updateOptions</code> method:</p>
<pre>g.updateOptions({
new_option1: value1,
new_option2: value2
});
</pre>
-
<p>And, without further ado, here's the complete list of options:</p>
"""
for label in sorted(labels):
examples_html = ' '.join(
'<a href="%s">%s</a>' % (f, name(f)) for f in tests)
+ if 'parameters' in opt:
+ parameters = opt['parameters']
+ parameters_html = '\n'.join("<i>%s</i>: %s<br/>" % (p[0], p[1]) for p in parameters)
+ parameters_html = "\n <div class='parameters'>\n%s</div>" % (parameters_html);
+ else:
+ parameters_html = ''
+
if not opt['type']: opt['type'] = '(missing)'
if not opt['default']: opt['default'] = '(missing)'
if not opt['description']: opt['description'] = '(missing)'
print """
- <p class='option'><a name="%(name)s"/><b>%(name)s</b><br/>
+ <div class='option'><a name="%(name)s"></a><b>%(name)s</b><br/>
%(desc)s<br/>
- <i>Type: %(type)s<br/>
- Default: %(default)s</i><br/>
+ <i>Type: %(type)s</i><br/>%(parameters)s
+ <i>Default: %(default)s</i><br/>
Examples: %(examples_html)s<br/>
- <br/>
+ <br/></div>
""" % { 'name': opt_name,
'type': opt['type'],
+ 'parameters': parameters_html,
'default': opt['default'],
'desc': opt['description'],
'examples_html': examples_html}
print """
+<a name="point_properties"></a><h3>Point Properties</h3>
+Some callbacks take a point argument. Its properties are:<br/>
+<ul>
+<li>xval/yval: The data coordinates of the point (with dates/times as millis since epoch)</li>
+<li>canvasx/canvasy: The canvas coordinates at which the point is drawn.</li>
+<li>name: The name of the data series to which the point belongs</li>
+</ul>
</div>
</body>
</html>
./generate-jsdoc.sh
# Copy everything to the site.
- scp -r gallery tests jsdoc experimental $site \
+ scp -r gallery common tests jsdoc experimental $site \
&& \
scp dygraph*.js gadget.xml excanvas.js thumbnail.png screenshot.png docs/* $site/
else
data.addColumn('string', 'title2');
data.addColumn('string', 'text2');
data.addRows([
- [new Date(2008, 1 ,1), 30000, undefined, undefined, 40645, undefined, undefined],
- [new Date(2008, 1 ,2), 14045, undefined, undefined, 20374, undefined, undefined],
- [new Date(2008, 1 ,3), 55022, undefined, undefined, 50766, undefined, undefined],
- [new Date(2008, 1 ,4), 75284, undefined, undefined, 14334, 'Out of Stock','Ran out of stock on pens at 4pm'],
- [new Date(2008, 1 ,5), 41476, 'Bought Pens','Bought 200k pens', 66467, undefined, undefined],
- [new Date(2008, 1 ,6), 33322, undefined, undefined, 39463, undefined, undefined]
+ [new Date(2008, 11 ,1), 30000, undefined, undefined, 40645, undefined, undefined],
+ [new Date(2008, 11 ,2), 14045, undefined, undefined, 20374, undefined, undefined],
+ [new Date(2008, 11 ,3), 55022, undefined, undefined, 50766, undefined, undefined],
+ [new Date(2008, 11 ,4), 75284, undefined, undefined, 14334, 'Out of Stock','Ran out of stock on pens at 4pm'],
+ [new Date(2008, 11 ,5), 41476, 'Bought Pens','Bought 200k pens', 66467, undefined, undefined],
+ [new Date(2008, 11 ,6), 33322, undefined, undefined, 39463, undefined, undefined]
]);
+ for (var i = 1; i < 14; i++) {
+ data.addRows([
+ [new Date(2008, 11 , 6 + i), i * 1000, 'title1-' + i, 'text1 ' + (i * 1000 - i), (i * 2000 + i), 'title2-' + i, 'text2' + i * 1000],
+ ]);
+ }
+
var chart = new google.visualization.AnnotatedTimeLine(document.getElementById('gviz_div'));
chart.draw(data, {displayAnnotations: true});
<div id='dg_div' style='width: 700px; height: 240px;'></div>
</body>
</html>
-
--- /dev/null
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9">
+ <title>Custom Circles</title>
+ <!--[if IE]>
+ <script type="text/javascript" src="../excanvas.js"></script>
+ <![endif]-->
+ <!--
+ For production (minified) code, use:
+ <script type="text/javascript" src="dygraph-combined.js"></script>
+ -->
+ <script type="text/javascript" src="../dygraph-dev.js"></script>
+
+ </head>
+ <body>
+ <h2>Custom circles and hover circles</h2>
+ <div id="demodiv"></div>
+
+ <script type="text/javascript">
+ var smile = function(g, series, ctx, cx, cy, color, radius) {
+ mouthlessFace(g, series, ctx, cx, cy, color, radius);
+
+ ctx.fillStyle = "#000000";
+ ctx.beginPath();
+ ctx.arc(cx, cy, radius - 2, .3, Math.PI - .3, false);
+ ctx.stroke();
+ };
+
+ var frown = function(g, series, ctx, cx, cy, color, radius) {
+ mouthlessFace(g, series, ctx, cx, cy, color, radius);
+
+ ctx.fillStyle = "#000000";
+ ctx.beginPath();
+ ctx.arc(cx, cy + radius, radius - 2, Math.PI + .3, -.3, false);
+ ctx.stroke();
+ };
+
+ var mouthlessFace = function(g, series, ctx, cx, cy, color, radius) {
+ ctx.strokeStyle = "#000000";
+ ctx.fillStyle = "#FFFF00";
+ ctx.beginPath();
+ ctx.arc(cx, cy, radius, Math.PI * 2, false);
+ ctx.closePath();
+ ctx.stroke();
+ ctx.fill();
+
+ ctx.fillStyle = "#000000";
+ ctx.beginPath();
+ ctx.arc(cx - (radius / 3) , cy - (radius / 4), 1, Math.PI * 2, false);
+ ctx.closePath();
+ ctx.stroke();
+ ctx.fill();
+
+ ctx.beginPath();
+ ctx.arc(cx + (radius / 3) , cy - (radius / 4), 1, Math.PI * 2, false);
+ ctx.closePath();
+ ctx.stroke();
+ ctx.fill();
+ };
+
+ g = new Dygraph(
+ document.getElementById("demodiv"),
+ function() {
+
+ var r = "xval,default,triangle,square,diamond,pentagon,hexagon,circle,star,plus,ex,custom\n";
+ for (var i=1; i<=20; i++) {
+ r += i;
+ for (var j = 0; j < 11; j++) {
+ r += "," + j + (i / 3);
+ }
+ r += "\n";
+ }
+ return r;
+ },
+ {
+ drawPoints : true,
+ pointSize : 5,
+ highlightCircleSize : 8,
+ 'default' : {
+ drawPointCallback : Dygraph.Circles.DEFAULT,
+ drawHighlightPointCallback : Dygraph.Circles.DEFAULT
+ },
+ 'triangle' : {
+ drawPointCallback : Dygraph.Circles.TRIANGLE,
+ drawHighlightPointCallback : Dygraph.Circles.TRIANGLE
+ },
+ 'square' : {
+ drawPointCallback : Dygraph.Circles.SQUARE,
+ drawHighlightPointCallback : Dygraph.Circles.SQUARE
+ },
+ 'diamond' : {
+ drawPointCallback : Dygraph.Circles.DIAMOND,
+ drawHighlightPointCallback : Dygraph.Circles.DIAMOND
+ },
+ 'pentagon' : {
+ drawPointCallback : Dygraph.Circles.PENTAGON,
+ drawHighlightPointCallback : Dygraph.Circles.PENTAGON
+ },
+ 'hexagon' : {
+ drawPointCallback : Dygraph.Circles.HEXAGON,
+ drawHighlightPointCallback : Dygraph.Circles.HEXAGON
+ },
+ 'circle' : {
+ drawPointCallback : Dygraph.Circles.CIRCLE,
+ drawHighlightPointCallback : Dygraph.Circles.CIRCLE
+ },
+ 'star' : {
+ drawPointCallback : Dygraph.Circles.STAR,
+ drawHighlightPointCallback : Dygraph.Circles.STAR
+ },
+ 'plus' : {
+ drawPointCallback : Dygraph.Circles.PLUS,
+ drawHighlightPointCallback : Dygraph.Circles.PLUS
+ },
+ 'ex' : {
+ drawPointCallback : Dygraph.Circles.EX,
+ drawHighlightPointCallback : Dygraph.Circles.EX
+ },
+ 'custom' : {
+ drawPointCallback : frown,
+ drawHighlightPointCallback : smile
+ }
+ }
+ );
+ </script>
+</body>
+</html>
<body>
<p>Plot which can be easily generated with different numbers of points for
benchmarking/profiling and improving performance of dygraphs.</p>
- <p>Data to plot:
- <input type="radio" id="sine" name="group1" value="sine"
- onclick="clickedRadioButton(this);" checked> sinusoid function
- <input type="radio" id="rand" name="group1" value="rand"
- onclick="clickedRadioButton(this);"> random points <br></p>
- <p>Number of points:
- <input type="text" id="points" size="20"></p>
- <p>Number of series:
- <input type="text" id="series" size="20"></p>
- <p>Roll period (in points):
- <input type="text" id="rollPeriod" size="20"></p>
- <p>Repetitions:
- <input type="text" id="repetitions" size="20"></p>
-
- <input type="button" value="Go!" onclick="updatePlot();">
+ <div id='parameters'>
+ <p>Data to plot:
+ <input type="radio" id="sine" name="group1" value="sine"
+ onclick="clickedRadioButton(this);" checked> sinusoid function
+ <input type="radio" id="rand" name="group1" value="rand"
+ onclick="clickedRadioButton(this);"> random points <br></p>
+ <p>Number of points:
+ <input type="text" id="points" size="20"></p>
+ <p>Number of series:
+ <input type="text" id="series" size="20"></p>
+ <p>Roll period (in points):
+ <input type="text" id="rollPeriod" size="20"></p>
+ <p>Repetitions:
+ <input type="text" id="repetitions" size="20"></p>
+ <input type="button" value="Go!" onclick="updatePlot();">
+ </div>
<br>
<br>
<div id="plot"></div>
<div id="metaperformance"></div>
<script type="text/javascript">
- var plot;
+ var graph = null;
+ var metrics = null;
var dataType = "sine";
var durations = [];
var opts = {labels: labels, rollPeriod: rollPeriod, timingName: "x"};
var millisecondss = [];
for (var i = 0; i < repetitions; i++) {
+ if (graph != null) {
+ graph.destroy(); // release memory from prior graph.
+ }
var start = new Date();
- plot = new Dygraph(plotDiv, data, opts);
+ graph = new Dygraph(plotDiv, data, opts);
var end = new Date();
durations.push([start, end - start]);
millisecondss.push(end - start);
if (durations.length > 0) {
var start2 = new Date();
- new Dygraph(
- document.getElementById('metrics'),
- durations,
- {
- highlightCircleSize: 4,
- labels: [ "Date", "ms" ]
- });
+ if (!metrics) {
+ metrics = new Dygraph(
+ document.getElementById('metrics'),
+ durations,
+ {
+ highlightCircleSize: 4,
+ labels: [ "Date", "ms" ]
+ });
+ } else {
+ metrics.updateOptions({file: durations});
+ }
var end2 = new Date();
document.getElementById("metaperformance").innerHTML =
"completed in " + (end2 - start2) + " milliseconds.";
function dblClickV3(event, g, context) {
// Reducing by 20% makes it 80% the original size, which means
// to restore to original size it must grow by 25%
+
+ if (!(event.offsetX && event.offsetY)){
+ event.offsetX = event.layerX - event.target.offsetLeft;
+ event.offsetY = event.layerY - event.target.offsetTop;
+ }
+
var percentages = offsetToPercentage(g, event.offsetX, event.offsetY);
var xPct = percentages[0];
var yPct = percentages[1];
// that verbatim, it would be a 7.5%.
var percentage = normal / 50;
+ if (!(event.offsetX && event.offsetY)){
+ event.offsetX = event.layerX - event.target.offsetLeft;
+ event.offsetY = event.layerY - event.target.offsetTop;
+ }
+
var percentages = offsetToPercentage(g, event.offsetX, event.offsetY);
var xPct = percentages[0];
var yPct = percentages[1];
}
function dblClickV4(event, g, context) {
- restorePositioning(g4);
+ restorePositioning(g);
}
function drawV4(x, y) {
<body>
<h2>Chart with per-series properties</h2>
<div id="demodiv"></div>
-
+ <h2>Chart with per-series properties with legend.</h2>
+ <div id="demodiv2"></div>
<script type="text/javascript">
+ data = function() {
+ var zp = function(x) { if (x < 10) return "0"+x; else return x; };
+ var r = "date,parabola,line,another line,sine wave,sine wave2\n";
+ for (var i=1; i<=31; i++) {
+ r += "200610" + zp(i);
+ r += "," + 10*(i*(31-i));
+ r += "," + 10*(8*i);
+ r += "," + 10*(250 - 8*i);
+ r += "," + 10*(125 + 125 * Math.sin(0.3*i));
+ r += "," + 10*(125 + 125 * Math.sin(0.3*i+Math.PI));
+ r += "\n";
+ }
+ return r;
+ };
g = new Dygraph(
document.getElementById("demodiv"),
- function() {
- var zp = function(x) { if (x < 10) return "0"+x; else return x; };
- var r = "date,parabola,line,another line,sine wave\n";
- for (var i=1; i<=31; i++) {
- r += "200610" + zp(i);
- r += "," + 10*(i*(31-i));
- r += "," + 10*(8*i);
- r += "," + 10*(250 - 8*i);
- r += "," + 10*(125 + 125 * Math.sin(0.3*i));
- r += "\n";
- }
- return r;
- },
+ data,
{
strokeWidth: 2,
'parabola': {
'sine wave': {
strokeWidth: 3,
highlightCircleSize: 10
+ },
+ 'sine wave2': {
+ strokePattern: [10, 2, 5, 2],
+ strokeWidth: 2,
+ highlightCircleSize: 3
+ }
+ }
+ );
+ g2 = new Dygraph(
+ document.getElementById("demodiv2"),
+ data,
+ {
+ legend: 'always',
+ strokeWidth: 2,
+ 'parabola': {
+ strokePattern: null,
+ drawPoints: true,
+ pointSize: 4,
+ highlightCircleSize: 6
+ },
+ 'line': {
+ strokePattern: Dygraph.DASHED_LINE,
+ strokeWidth: 1.0,
+ drawPoints: true,
+ pointSize: 1.5
+ },
+ 'another line': {
+ strokePattern: [25, 5]
+ },
+ 'sine wave': {
+ strokePattern: Dygraph.DOTTED_LINE,
+ strokeWidth: 3,
+ highlightCircleSize: 10
+ },
+ 'sine wave2': {
+ strokePattern: Dygraph.DOT_DASH_LINE,
+ strokeWidth: 2,
+ highlightCircleSize: 3
}
}
);
+
</script>
</body>
</html>
--- /dev/null
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9">
+ <title>Multiple y-axes with valueRange</title>
+ <!--[if IE]>
+ <script type="text/javascript" src="../excanvas.js"></script>
+ <![endif]-->
+ <!--
+ For production (minified) code, use:
+ <script type="text/javascript" src="dygraph-combined.js"></script>
+ -->
+ <script type="text/javascript" src="../dygraph-dev.js"></script>
+
+ </head>
+ <body>
+ <h2>Multiple y-axes with valueRange</h2>
+ <p>The same data with both different valueRanges. Two-axis old y[40, 70] valueRange:</p>
+ <div id="demodiv" style="width: 640; height: 350; border: 1px solid black"></div>
+ <p>Two-axis new valueRange y[40, 80] set:</p>
+ <div id="demodiv_one" style="width: 640; height: 350; border: 1px solid black"></div>
+ <p>Two-axis new valueRange y[40, 80] & y2[1e6, 1.2e6] set:</p>
+ <div id="demodiv_two" style="width: 640; height: 350; border: 1px solid black"></div>
+ <script type="text/javascript">
+ var data = [];
+ for (var i = 1; i <= 100; i++) {
+ var m = "01", d = i;
+ if (d > 31) { m = "02"; d -= 31; }
+ if (m == "02" && d > 28) { m = "03"; d -= 28; }
+ if (m == "03" && d > 31) { m = "04"; d -= 31; }
+ if (d < 10) d = "0" + d;
+ // two series, one with range 1-100, one with range 1-2M
+ data.push([new Date("2010/" + m + "/" + d),
+ i,
+ 100 - i,
+ 1e6 * (1 + i * (100 - i) / (50 * 50)),
+ 1e6 * (2 - i * (100 - i) / (50 * 50))]);
+ }
+
+ g = new Dygraph(
+ document.getElementById("demodiv"),
+ data,
+ {
+ labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ],
+ 'Y3': {
+ axis: {
+ }
+ },
+ 'Y4': {
+ axis: 'Y3' // use the same y-axis as series Y3
+ },
+ valueRange: [40, 70],
+ axes: {
+ y2: {
+ // set axis-related properties here
+ labelsKMB: true
+ }
+ },
+ ylabel: 'Primary y-axis',
+ y2label: 'Secondary y-axis',
+ yAxisLabelWidth: 60
+ }
+ );
+
+ g2 = new Dygraph(
+ document.getElementById("demodiv_one"),
+ data,
+ {
+ labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ],
+ 'Y3': {
+ axis: {
+ }
+ },
+ 'Y4': {
+ axis: 'Y3' // use the same y-axis as series Y3
+ },
+ axes: {
+ y: {
+ valueRange: [40, 80]
+ },
+ y2: {
+ // set axis-related properties here
+ labelsKMB: true
+ }
+ },
+ ylabel: 'Primary y-axis',
+ y2label: 'Secondary y-axis',
+ yAxisLabelWidth: 60
+ }
+ );
+
+ g2 = new Dygraph(
+ document.getElementById("demodiv_two"),
+ data,
+ {
+ labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ],
+ 'Y3': {
+ axis: {
+ }
+ },
+ 'Y4': {
+ axis: 'Y3' // use the same y-axis as series Y3
+ },
+ axes: {
+ y: {
+ valueRange: [40, 80]
+ },
+ y2: {
+ // set axis-related properties here
+ valueRange: [1e6, 1.2e6],
+ labelsKMB: true
+ }
+ },
+ ylabel: 'Primary y-axis',
+ y2label: 'Secondary y-axis',
+ yAxisLabelWidth: 60
+ }
+ );
+
+
+ </script>
+</body>
+</html>