// default URLs for all the external files we need
// make them RELATIVE so that the whole thing can also be
// installed under a web server
- NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-1.11.3.min.js';
- NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity.min.js';
- NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline.min.js';
- NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined.js';
- NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-min.js';
- NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris.min.js';
- NETDATA.morris_css = NETDATA.serverDefault + 'css/morris.css';
- NETDATA.dashboard_css = NETDATA.serverDefault + 'dashboard.css';
- NETDATA.google_js = 'https://www.google.com/jsapi';
+ NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-1.11.3.min.js';
+ NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity.min.js';
+ NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline.min.js';
+ NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined.js';
+ NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter.js';
+ NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-min.js';
+ NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris.min.js';
+ NETDATA.morris_css = NETDATA.serverDefault + 'css/morris.css';
+ NETDATA.dashboard_css = NETDATA.serverDefault + 'dashboard.css';
+ NETDATA.google_js = 'https://www.google.com/jsapi';
// these are the colors Google Charts are using
// we have them here to attempt emulate their look and feel on the other chart libraries
},
resetChart: function() {
- if(NETDATA.globalPanAndZoom.isMaster(this)) {
- NETDATA.globalPanAndZoom.clearMaster();
- // it will call us back - no need to do
- // anything more.
- return;
- }
+ NETDATA.globalPanAndZoom.clearMaster();
+ this.follows_global = 0;
- //if(state.current.name != 'auto')
- this.setMode('auto');
+ this.clearSelection();
+ this.setMode('auto');
this.current.force_before_ms = null;
this.current.force_after_ms = null;
this.current.last_autorefreshed = 0;
- this.follows_global = 0;
this.paused = false;
this.selected = false;
this.enabled = true;
// update the performance counters
var now = new Date().getTime();
- // don't update last_update_ms if this chart is
+ // don't update last_autorefreshed if this chart is
// forced to be updated with global PanAndZoom
if(NETDATA.globalPanAndZoom.isActive())
this.current.last_autorefreshed = 0;
// dygraph
NETDATA.dygraph = {
+ smooth: false,
state: null,
sync: false,
dont_sync_before: 0,
if(NETDATA.options.debug.dygraph) state.log('dygraph.resetChart()');
state.resetChart();
- if(NETDATA.globalPanAndZoom.clearMaster());
}
NETDATA.dygraph.chartPanOrZoom = function(state, dygraph, after, before) {
return true;
}
+ NETDATA.dygraphSmoothInitialize = function(callback) {
+ $.ajax({
+ url: NETDATA.dygraph_smooth_js,
+ cache: true,
+ dataType: "script"
+ })
+ .done(function() {
+ NETDATA.dygraph.smooth = true;
+ smoothPlotter.smoothing = 0.3;
+ })
+ .always(function() {
+ if(typeof callback == "function")
+ callback();
+ })
+ };
+
NETDATA.dygraphInitialize = function(callback) {
if(typeof netdataNoDygraphs == 'undefined' || !netdataNoDygraphs) {
$.ajax({
})
.done(function() {
NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
+ NETDATA.dygraphSmoothInitialize(callback);
})
.fail(function() {
NETDATA.error(100, NETDATA.dygraph_js);
- })
- .always(function() {
if(typeof callback == "function")
callback();
})
if(NETDATA.options.debug.dygraph || state.debug) state.log('dygraphChartCreate()');
var self = $(state.element);
- var title = self.data('dygraph-title') || state.chart.title;
- var titleHeight = self.data('dygraph-titleheight') || 19;
- var hideOverlayOnMouseOut = self.data('dygraph-hideoverlayonmouseout') || true;
- var colors = self.data('dygraph-colors') || NETDATA.colors;
-
- // legend
- var legend = self.data('dygraph-legend') || 'onmouseover';
- var labelsDiv = self.data('dygraph-labelsdiv') || undefined;
- var labelsDivStyles = self.data('dygraph-labelsdivstyles') || { 'fontSize':'10px' };
- var labelsDivWidth = self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70;
- var labelsSeparateLines = self.data('dygraph-labelsseparatelines') || false;
- var labelsShowZeroValues = self.data('dygraph-labelsshowzerovalues') || true;
- var showLabelsOnHighlight = self.data('dygraph-showlabelsonhighlight') || true;
- var yLabelWidth = self.data('dygraph-ylabelwidth') || 12;
-
- // Value Formatting
- var sigFigs = self.data('dygraph-sigfigs') || null;
- var maxNumberWidth = self.data('dygraph-maxnumberwidth') || 8;
- var digitsAfterDecimal = self.data('dygraph-digitsafterdecimal') || 2;
- var valueFormatter = self.data('dygraph-valueformatter') || undefined; //function(x){ return x.toFixed(2); };
-
- // Axis and Grid
- var drawAxis = self.data('dygraph-drawaxis') || true;
- var axisLabelFontSize = self.data('dygraph-axislabelfontsize') || 10;
- var axisLineWidth = self.data('dygraph-axislinewidth') || 0.3;
- var gridLineColor = self.data('dygraph-gridlinecolor') || '#EEE';
- var axisLineColor = self.data('dygraph-axislinecolor') || '#DDD';
- var drawGrid = self.data('dygraph-drawgrid') || true;
- var drawXGrid = self.data('dygraph-drawxgrid') || undefined;
- var drawYGrid = self.data('dygraph-drawygrid') || undefined;
- var gridLinePattern = self.data('dygraph-gridlinepattern') || null;
- var gridLineWidth = self.data('dygraph-gridlinewidth') || 0.3;
-
- // enabling this makes the chart with little square lines
- var stepPlot = self.data('dygraph-stepplot') || false;
- var connectSeparatedPoints = self.data('dygraph-connectseparatedpoints') || false;
-
- // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
- var drawGapEdgePoints = self.data('dygraph-drawgapedgepoints') || true;
-
- // 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.
- var drawPoints = self.data('dygraph-drawpoints') || false;
- var pointSize = self.data('dygraph-pointsize') || 1;
-
- // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
- var strokeWidth = self.data('dygraph-strokewidth') || (state.chart.chart_type == 'stacked')?0.0:1.0;
- var strokePattern = self.data('dygraph-strokepattern') || undefined;
-
- // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
- var strokeBorderColor = self.data('dygraph-strokebordercolor') || 'white';
- var strokeBorderWidth = self.data('dygraph-strokeborderwidth') || (state.chart.chart_type == 'stacked')?0.0:0.0;
-
- var highlightCircleSize = self.data('dygraph-highlightcirclesize') || 3;
- var highlightSeriesOpts = self.data('dygraph-highlightseriesopts') || null; // TOO SLOW: { strokeWidth: 1.5 };
- var highlightSeriesBackgroundAlpha = self.data('dygraph-highlightseriesbackgroundalpha') || null; // TOO SLOW: (state.chart.chart_type == 'stacked')?0.7:0.5;
- var pointClickCallback = self.data('dygraph-pointclickcallback') || undefined;
- var showRangeSelector = self.data('dygraph-showrangeselector') || false;
- var showRoller = self.data('dygraph-showroller') || false;
-
- // leave a few pixels empty on the right of the chart
- var rightGap = self.data('dygraph-rightgap') || 5;
-
- var fillGraph = self.data('dygraph-fillgraph') || (state.chart.chart_type == 'area')?true:false;
- var fillAlpha = self.data('dygraph-fillalpha') || (state.chart.chart_type == 'stacked')?0.8:0.2;
- var stackedGraph = self.data('dygraph-stackedgraph') || (state.chart.chart_type == 'stacked')?true:false;
- var stackedGraphNaNFill = self.data('dygraph-stackedgraphnanfill') || 'none';
state.dygraph_options = {
- title: title,
- titleHeight: titleHeight,
- ylabel: state.chart.units,
- yLabelWidth: yLabelWidth,
- connectSeparatedPoints: connectSeparatedPoints,
- drawPoints: drawPoints,
- fillGraph: fillGraph,
- stackedGraph: stackedGraph,
- stackedGraphNaNFill: stackedGraphNaNFill,
- drawGrid: drawGrid,
- drawXGrid: drawXGrid,
- drawYGrid: drawYGrid,
- gridLinePattern: gridLinePattern,
- gridLineWidth: gridLineWidth,
- gridLineColor: gridLineColor,
- axisLineColor: axisLineColor,
- axisLineWidth: axisLineWidth,
- drawAxis: drawAxis,
- hideOverlayOnMouseOut: hideOverlayOnMouseOut,
- labelsDiv: labelsDiv,
- labelsDivStyles: labelsDivStyles,
- labelsDivWidth: labelsDivWidth,
- labelsSeparateLines: labelsSeparateLines,
- labelsShowZeroValues: labelsShowZeroValues,
+ colors: self.data('dygraph-colors') || NETDATA.colors,
+
+ // leave a few pixels empty on the right of the chart
+ rightGap: self.data('dygraph-rightgap') || 5,
+ showRangeSelector: self.data('dygraph-showrangeselector') || false,
+ showRoller: self.data('dygraph-showroller') || false,
+
+ title: self.data('dygraph-title') || state.chart.title,
+ titleHeight: self.data('dygraph-titleheight') || 19,
+
+ legend: self.data('dygraph-legend') || 'onmouseover',
+ labels: data.result.labels,
+ labelsDiv: self.data('dygraph-labelsdiv') || undefined,
+ labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'10px' },
+ labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
+ labelsSeparateLines: self.data('dygraph-labelsseparatelines') || false,
+ labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
labelsKMB: false,
labelsKMG2: false,
- legend: legend,
- showLabelsOnHighlight: showLabelsOnHighlight,
- maxNumberWidth: maxNumberWidth,
- sigFigs: sigFigs,
- digitsAfterDecimal: digitsAfterDecimal,
- axisLabelFontSize: axisLabelFontSize,
- colors: colors,
- fillAlpha: fillAlpha,
- strokeWidth: strokeWidth,
- drawGapEdgePoints: drawGapEdgePoints,
- pointSize: pointSize,
- stepPlot: stepPlot,
- strokeBorderColor: strokeBorderColor,
- strokeBorderWidth: strokeBorderWidth,
- strokePattern: strokePattern,
- highlightCircleSize: highlightCircleSize,
- highlightSeriesOpts: highlightSeriesOpts,
- highlightSeriesBackgroundAlpha: highlightSeriesBackgroundAlpha,
- pointClickCallback: pointClickCallback,
- showRangeSelector: showRangeSelector,
- showRoller: showRoller,
- valueFormatter: valueFormatter,
- rightGap: rightGap,
- labels: data.result.labels,
+ showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
+ hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
+
+ ylabel: state.chart.units,
+ yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
+
+ // the function to plot the chart
+ plotter: null,
+
+ // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
+ strokeWidth: self.data('dygraph-strokewidth') || (state.chart.chart_type == 'stacked')?0.0:1.0,
+ strokePattern: self.data('dygraph-strokepattern') || undefined,
+
+ // 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.
+ drawPoints: self.data('dygraph-drawpoints') || false,
+
+ // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
+ drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
+
+ connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
+ pointSize: self.data('dygraph-pointsize') || 1,
+
+ // enabling this makes the chart with little square lines
+ stepPlot: self.data('dygraph-stepplot') || false,
+
+ // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
+ strokeBorderColor: self.data('dygraph-strokebordercolor') || 'white',
+ strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (state.chart.chart_type == 'stacked')?0.0:0.0,
+
+ fillGraph: self.data('dygraph-fillgraph') || (state.chart.chart_type == 'area')?true:false,
+ fillAlpha: self.data('dygraph-fillalpha') || (state.chart.chart_type == 'stacked')?0.8:0.2,
+ stackedGraph: self.data('dygraph-stackedgraph') || (state.chart.chart_type == 'stacked')?true:false,
+ stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
+
+ drawAxis: self.data('dygraph-drawaxis') || true,
+ axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
+ axisLineColor: self.data('dygraph-axislinecolor') || '#DDD',
+ axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
+
+ drawGrid: self.data('dygraph-drawgrid') || true,
+ drawXGrid: self.data('dygraph-drawxgrid') || undefined,
+ drawYGrid: self.data('dygraph-drawygrid') || undefined,
+ gridLinePattern: self.data('dygraph-gridlinepattern') || null,
+ gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
+ gridLineColor: self.data('dygraph-gridlinecolor') || '#EEE',
+
+ maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
+ sigFigs: self.data('dygraph-sigfigs') || null,
+ digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
+ valueFormatter: self.data('dygraph-valueformatter') || undefined, //function(x){ return x.toFixed(2); };,
+
+ highlightCircleSize: self.data('dygraph-highlightcirclesize') || 4,
+ highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
+ highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (state.chart.chart_type == 'stacked')?0.7:0.5,
+
+ pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
axes: {
x: {
pixelsPerLabel: 50,
if(NETDATA.options.debug.dygraph || state.debug) state.log('dygraphHighlightCallback()');
state.pauseChart();
NETDATA.dygraph.syncStart(state, event, x, points, row, seriesName);
+ console.log(state.dygraph_instance);
+ // fix legend zIndex using the internal structures of dygraph legend module
+ // this works, but it is a hack!
+ // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
},
unhighlightCallback: function(event) {
if(NETDATA.options.debug.dygraph || state.debug) state.log('dygraphUnhighlightCallback()');
}
};
+ // smooth lines patch
+ if(NETDATA.dygraph.smooth && state.chart.chart_type == 'line') {
+ state.dygraph_options.plotter = smoothPlotter;
+ state.dygraph_options.strokeWidth = 1.5;
+ }
+
+ var theme = self.data('dygraph-theme') || null;
+ if(theme == 'sparkline') {
+ state.dygraph_options.drawGrid = false;
+ state.dygraph_options.drawAxis = false;
+ state.dygraph_options.title = undefined;
+ state.dygraph_options.units = undefined;
+ state.dygraph_options.legend = 'never'; // 'follow'
+ state.dygraph_options.ylabel = undefined;
+ state.dygraph_options.yLabelWidth = 0;
+ // state.dygraph_options.labelsDivWidth = 120;
+ state.dygraph_options.labelsSeparateLines = true;
+ state.dygraph_options.highlightCircleSize = 3;
+ state.dygraph_options.rightGap = 0;
+ }
+
state.dygraph_instance = new Dygraph(state.element_chart,
data.result.data, state.dygraph_options);
};
--- /dev/null
+var smoothPlotter = (function() {
+"use strict";
+
+/**
+ * Given three sequential points, p0, p1 and p2, find the left and right
+ * control points for p1.
+ *
+ * The three points are expected to have x and y properties.
+ *
+ * The alpha parameter controls the amount of smoothing.
+ * If α=0, then both control points will be the same as p1 (i.e. no smoothing).
+ *
+ * Returns [l1x, l1y, r1x, r1y]
+ *
+ * It's guaranteed that the line from (l1x, l1y)-(r1x, r1y) passes through p1.
+ * Unless allowFalseExtrema is set, then it's also guaranteed that:
+ * l1y ∈ [p0.y, p1.y]
+ * r1y ∈ [p1.y, p2.y]
+ *
+ * The basic algorithm is:
+ * 1. Put the control points l1 and r1 α of the way down (p0, p1) and (p1, p2).
+ * 2. Shift l1 and r2 so that the line l1–r1 passes through p1
+ * 3. Adjust to prevent false extrema while keeping p1 on the l1–r1 line.
+ *
+ * This is loosely based on the HighCharts algorithm.
+ */
+function getControlPoints(p0, p1, p2, opt_alpha, opt_allowFalseExtrema) {
+ var alpha = (opt_alpha !== undefined) ? opt_alpha : 1/3; // 0=no smoothing, 1=crazy smoothing
+ var allowFalseExtrema = opt_allowFalseExtrema || false;
+
+ if (!p2) {
+ return [p1.x, p1.y, null, null];
+ }
+
+ // Step 1: Position the control points along each line segment.
+ var l1x = (1 - alpha) * p1.x + alpha * p0.x,
+ l1y = (1 - alpha) * p1.y + alpha * p0.y,
+ r1x = (1 - alpha) * p1.x + alpha * p2.x,
+ r1y = (1 - alpha) * p1.y + alpha * p2.y;
+
+ // Step 2: shift the points up so that p1 is on the l1–r1 line.
+ if (l1x != r1x) {
+ // This can be derived w/ some basic algebra.
+ var deltaY = p1.y - r1y - (p1.x - r1x) * (l1y - r1y) / (l1x - r1x);
+ l1y += deltaY;
+ r1y += deltaY;
+ }
+
+ // Step 3: correct to avoid false extrema.
+ if (!allowFalseExtrema) {
+ if (l1y > p0.y && l1y > p1.y) {
+ l1y = Math.max(p0.y, p1.y);
+ r1y = 2 * p1.y - l1y;
+ } else if (l1y < p0.y && l1y < p1.y) {
+ l1y = Math.min(p0.y, p1.y);
+ r1y = 2 * p1.y - l1y;
+ }
+
+ if (r1y > p1.y && r1y > p2.y) {
+ r1y = Math.max(p1.y, p2.y);
+ l1y = 2 * p1.y - r1y;
+ } else if (r1y < p1.y && r1y < p2.y) {
+ r1y = Math.min(p1.y, p2.y);
+ l1y = 2 * p1.y - r1y;
+ }
+ }
+
+ return [l1x, l1y, r1x, r1y];
+}
+
+
+// A plotter which uses splines to create a smooth curve.
+// See tests/plotters.html for a demo.
+// Can be controlled via smoothPlotter.smoothing
+function smoothPlotter(e) {
+ var ctx = e.drawingContext,
+ points = e.points;
+
+ ctx.beginPath();
+ ctx.moveTo(points[0].canvasx, points[0].canvasy);
+
+ // right control point for previous point
+ var lastRightX = points[0].canvasx, lastRightY = points[0].canvasy;
+ var isOK = Dygraph.isOK; // i.e. is none of (null, undefined, NaN)
+
+ for (var i = 1; i < points.length; i++) {
+ var p0 = points[i - 1],
+ p1 = points[i],
+ p2 = points[i + 1];
+ p0 = p0 && isOK(p0.canvasy) ? p0 : null;
+ p1 = p1 && isOK(p1.canvasy) ? p1 : null;
+ p2 = p2 && isOK(p2.canvasy) ? p2 : null;
+ if (p0 && p1) {
+ var controls = getControlPoints({x: p0.canvasx, y: p0.canvasy},
+ {x: p1.canvasx, y: p1.canvasy},
+ p2 && {x: p2.canvasx, y: p2.canvasy},
+ smoothPlotter.smoothing);
+ // Uncomment to show the control points:
+ // ctx.lineTo(lastRightX, lastRightY);
+ // ctx.lineTo(controls[0], controls[1]);
+ // ctx.lineTo(p1.canvasx, p1.canvasy);
+ lastRightX = (lastRightX !== null) ? lastRightX : p0.canvasx;
+ lastRightY = (lastRightY !== null) ? lastRightY : p0.canvasy;
+ ctx.bezierCurveTo(lastRightX, lastRightY,
+ controls[0], controls[1],
+ p1.canvasx, p1.canvasy);
+ lastRightX = controls[2];
+ lastRightY = controls[3];
+ } else if (p1) {
+ // We're starting again after a missing point.
+ ctx.moveTo(p1.canvasx, p1.canvasy);
+ lastRightX = p1.canvasx;
+ lastRightY = p1.canvasy;
+ } else {
+ lastRightX = lastRightY = null;
+ }
+ }
+
+ ctx.stroke();
+}
+smoothPlotter.smoothing = 1/3;
+smoothPlotter._getControlPoints = getControlPoints; // for testing
+
+return smoothPlotter;
+
+})();
+++ /dev/null
-/**
- * Synchronize zooming and/or selections between a set of dygraphs.
- *
- * Usage:
- *
- * var g1 = new Dygraph(...),
- * g2 = new Dygraph(...),
- * ...;
- * var sync = Dygraph.synchronize(g1, g2, ...);
- * // charts are now synchronized
- * sync.detach();
- * // charts are no longer synchronized
- *
- * You can set options using the last parameter, for example:
- *
- * var sync = Dygraph.synchronize(g1, g2, g3, {
- * selection: true,
- * zoom: true
- * });
- *
- * The default is to synchronize both of these.
- *
- * Instead of passing one Dygraph object as each parameter, you may also pass an
- * array of dygraphs:
- *
- * var sync = Dygraph.synchronize([g1, g2, g3], {
- * selection: false,
- * zoom: true
- * });
- *
- * You may also set `range: false` if you wish to only sync the x-axis.
- * The `range` option has no effect unless `zoom` is true (the default).
- */
-(function() {
-/* global Dygraph:false */
-'use strict';
-
-var Dygraph;
-if (window.Dygraph) {
- Dygraph = window.Dygraph;
-} else if (typeof(module) !== 'undefined') {
- Dygraph = require('../dygraph');
-}
-
-var synchronize = function(/* dygraphs..., opts */) {
- if (arguments.length === 0) {
- throw 'Invalid invocation of Dygraph.synchronize(). Need >= 1 argument.';
- }
-
- var OPTIONS = ['selection', 'zoom', 'range'];
- var opts = {
- selection: true,
- zoom: true,
- range: true
- };
- var dygraphs = [];
- var prevCallbacks = [];
-
- var parseOpts = function(obj) {
- if (!(obj instanceof Object)) {
- throw 'Last argument must be either Dygraph or Object.';
- } else {
- for (var i = 0; i < OPTIONS.length; i++) {
- var optName = OPTIONS[i];
- if (obj.hasOwnProperty(optName)) opts[optName] = obj[optName];
- }
- }
- };
-
- if (arguments[0] instanceof Dygraph) {
- // Arguments are Dygraph objects.
- for (var i = 0; i < arguments.length; i++) {
- if (arguments[i] instanceof Dygraph) {
- dygraphs.push(arguments[i]);
- } else {
- break;
- }
- }
- if (i < arguments.length - 1) {
- throw 'Invalid invocation of Dygraph.synchronize(). ' +
- 'All but the last argument must be Dygraph objects.';
- } else if (i == arguments.length - 1) {
- parseOpts(arguments[arguments.length - 1]);
- }
- } else if (arguments[0].length) {
- // Invoked w/ list of dygraphs, options
- for (var i = 0; i < arguments[0].length; i++) {
- dygraphs.push(arguments[0][i]);
- }
- if (arguments.length == 2) {
- parseOpts(arguments[1]);
- } else if (arguments.length > 2) {
- throw 'Invalid invocation of Dygraph.synchronize(). ' +
- 'Expected two arguments: array and optional options argument.';
- } // otherwise arguments.length == 1, which is fine.
- } else {
- throw 'Invalid invocation of Dygraph.synchronize(). ' +
- 'First parameter must be either Dygraph or list of Dygraphs.';
- }
-
- if (dygraphs.length < 2) {
- throw 'Invalid invocation of Dygraph.synchronize(). ' +
- 'Need two or more dygraphs to synchronize.';
- }
-
- var readycount = dygraphs.length;
- for (var i = 0; i < dygraphs.length; i++) {
- var g = dygraphs[i];
- g.ready( function() {
- if (--readycount == 0) {
- // store original callbacks
- var callBackTypes = ['drawCallback', 'highlightCallback', 'unhighlightCallback'];
- for (var j = 0; j < dygraphs.length; j++) {
- if (!prevCallbacks[j]) {
- prevCallbacks[j] = {};
- }
- for (var k = callBackTypes.length - 1; k >= 0; k--) {
- prevCallbacks[j][callBackTypes[k]] = dygraphs[j].getFunctionOption(callBackTypes[k]);
- }
- }
-
- // Listen for draw, highlight, unhighlight callbacks.
- if (opts.zoom) {
- attachZoomHandlers(dygraphs, opts, prevCallbacks);
- }
-
- if (opts.selection) {
- attachSelectionHandlers(dygraphs, prevCallbacks);
- }
- }
- });
- }
-
- return {
- detach: function() {
- for (var i = 0; i < dygraphs.length; i++) {
- var g = dygraphs[i];
- if (opts.zoom) {
- g.updateOptions({drawCallback: prevCallbacks[i].drawCallback});
- }
- if (opts.selection) {
- g.updateOptions({
- highlightCallback: prevCallbacks[i].highlightCallback,
- unhighlightCallback: prevCallbacks[i].unhighlightCallback
- });
- }
- }
- // release references & make subsequent calls throw.
- dygraphs = null;
- opts = null;
- prevCallbacks = null;
- }
- };
-};
-
-function attachZoomHandlers(gs, syncOpts, prevCallbacks) {
- var block = false;
- for (var i = 0; i < gs.length; i++) {
- var g = gs[i];
- g.updateOptions({
- drawCallback: function(me, initial) {
- if (block || initial) return;
- block = true;
- var opts = {
- dateWindow: me.xAxisRange()
- };
- if (syncOpts.range) opts.valueRange = me.yAxisRange();
-
- for (var j = 0; j < gs.length; j++) {
- if (gs[j] == me) {
- if (prevCallbacks[j] && prevCallbacks[j].drawCallback) {
- prevCallbacks[j].drawCallback.apply(this, arguments);
- }
- continue;
- }
- gs[j].updateOptions(opts);
- }
- block = false;
- }
- }, true /* no need to redraw */);
- }
-}
-
-function attachSelectionHandlers(gs, prevCallbacks) {
- var block = false;
- for (var i = 0; i < gs.length; i++) {
- var g = gs[i];
-
- g.updateOptions({
- highlightCallback: function(event, x, points, row, seriesName) {
- if (block) return;
- block = true;
- var me = this;
- for (var i = 0; i < gs.length; i++) {
- if (me == gs[i]) {
- if (prevCallbacks[i] && prevCallbacks[i].highlightCallback) {
- prevCallbacks[i].highlightCallback.apply(this, arguments);
- }
- continue;
- }
- var idx = gs[i].getRowForX(x);
- if (idx !== null) {
- gs[i].setSelection(idx, seriesName);
- }
- }
- block = false;
- },
- unhighlightCallback: function(event) {
- if (block) return;
- block = true;
- var me = this;
- for (var i = 0; i < gs.length; i++) {
- if (me == gs[i]) {
- if (prevCallbacks[i] && prevCallbacks[i].unhighlightCallback) {
- prevCallbacks[i].unhighlightCallback.apply(this, arguments);
- }
- continue;
- }
- gs[i].clearSelection();
- }
- block = false;
- }
- }, true /* no need to redraw */);
- }
-}
-
-Dygraph.synchronize = synchronize;
-
-})();