2 * Synchronize zooming and/or selections between a set of dygraphs.
6 * var g1 = new Dygraph(...),
7 * g2 = new Dygraph(...),
9 * var sync = Dygraph.synchronize(g1, g2, ...);
10 * // charts are now synchronized
12 * // charts are no longer synchronized
14 * You can set options using the last parameter, for example:
16 * var sync = Dygraph.synchronize(g1, g2, g3, {
21 * The default is to synchronize both of these.
23 * Instead of passing one Dygraph object as each parameter, you may also pass an
26 * var sync = Dygraph.synchronize([g1, g2, g3], {
31 * You may also set `range: false` if you wish to only sync the x-axis.
32 * The `range` option has no effect unless `zoom` is true (the default).
34 * SPDX-License-Identifier: MIT
35 * Original source: https://github.com/danvk/dygraphs/blob/master/src/extras/synchronizer.js
38 /* global Dygraph:false */
43 Dygraph = window.Dygraph;
44 } else if (typeof(module) !== 'undefined') {
45 Dygraph = require('../dygraph');
48 var synchronize = function(/* dygraphs..., opts */) {
49 if (arguments.length === 0) {
50 throw 'Invalid invocation of Dygraph.synchronize(). Need >= 1 argument.';
53 var OPTIONS = ['selection', 'zoom', 'range'];
60 var prevCallbacks = [];
62 var parseOpts = function(obj) {
63 if (!(obj instanceof Object)) {
64 throw 'Last argument must be either Dygraph or Object.';
66 for (var i = 0; i < OPTIONS.length; i++) {
67 var optName = OPTIONS[i];
68 if (obj.hasOwnProperty(optName)) opts[optName] = obj[optName];
73 if (arguments[0] instanceof Dygraph) {
74 // Arguments are Dygraph objects.
75 for (var i = 0; i < arguments.length; i++) {
76 if (arguments[i] instanceof Dygraph) {
77 dygraphs.push(arguments[i]);
82 if (i < arguments.length - 1) {
83 throw 'Invalid invocation of Dygraph.synchronize(). ' +
84 'All but the last argument must be Dygraph objects.';
85 } else if (i == arguments.length - 1) {
86 parseOpts(arguments[arguments.length - 1]);
88 } else if (arguments[0].length) {
89 // Invoked w/ list of dygraphs, options
90 for (var i = 0; i < arguments[0].length; i++) {
91 dygraphs.push(arguments[0][i]);
93 if (arguments.length == 2) {
94 parseOpts(arguments[1]);
95 } else if (arguments.length > 2) {
96 throw 'Invalid invocation of Dygraph.synchronize(). ' +
97 'Expected two arguments: array and optional options argument.';
98 } // otherwise arguments.length == 1, which is fine.
100 throw 'Invalid invocation of Dygraph.synchronize(). ' +
101 'First parameter must be either Dygraph or list of Dygraphs.';
104 if (dygraphs.length < 2) {
105 throw 'Invalid invocation of Dygraph.synchronize(). ' +
106 'Need two or more dygraphs to synchronize.';
109 var readycount = dygraphs.length;
110 for (var i = 0; i < dygraphs.length; i++) {
112 g.ready( function() {
113 if (--readycount == 0) {
114 // store original callbacks
115 var callBackTypes = ['drawCallback', 'highlightCallback', 'unhighlightCallback'];
116 for (var j = 0; j < dygraphs.length; j++) {
117 if (!prevCallbacks[j]) {
118 prevCallbacks[j] = {};
120 for (var k = callBackTypes.length - 1; k >= 0; k--) {
121 prevCallbacks[j][callBackTypes[k]] = dygraphs[j].getFunctionOption(callBackTypes[k]);
125 // Listen for draw, highlight, unhighlight callbacks.
127 attachZoomHandlers(dygraphs, opts, prevCallbacks);
130 if (opts.selection) {
131 attachSelectionHandlers(dygraphs, prevCallbacks);
139 for (var i = 0; i < dygraphs.length; i++) {
142 g.updateOptions({drawCallback: prevCallbacks[i].drawCallback});
144 if (opts.selection) {
146 highlightCallback: prevCallbacks[i].highlightCallback,
147 unhighlightCallback: prevCallbacks[i].unhighlightCallback
151 // release references & make subsequent calls throw.
154 prevCallbacks = null;
159 function arraysAreEqual(a, b) {
160 if (!Array.isArray(a) || !Array.isArray(b)) return false;
162 if (i !== b.length) return false;
164 if (a[i] !== b[i]) return false;
169 function attachZoomHandlers(gs, syncOpts, prevCallbacks) {
171 for (var i = 0; i < gs.length; i++) {
174 drawCallback: function(me, initial) {
175 if (block || initial) return;
178 dateWindow: me.xAxisRange()
180 if (syncOpts.range) opts.valueRange = me.yAxisRange();
182 for (var j = 0; j < gs.length; j++) {
184 if (prevCallbacks[j] && prevCallbacks[j].drawCallback) {
185 prevCallbacks[j].drawCallback.apply(this, arguments);
190 // Only redraw if there are new options
191 if (arraysAreEqual(opts.dateWindow, gs[j].getOption('dateWindow')) &&
192 arraysAreEqual(opts.valueRange, gs[j].getOption('valueRange'))) {
196 gs[j].updateOptions(opts);
200 }, true /* no need to redraw */);
204 function attachSelectionHandlers(gs, prevCallbacks) {
206 for (var i = 0; i < gs.length; i++) {
210 highlightCallback: function(event, x, points, row, seriesName) {
214 for (var i = 0; i < gs.length; i++) {
216 if (prevCallbacks[i] && prevCallbacks[i].highlightCallback) {
217 prevCallbacks[i].highlightCallback.apply(this, arguments);
221 var idx = gs[i].getRowForX(x);
223 gs[i].setSelection(idx, seriesName);
228 unhighlightCallback: function(event) {
232 for (var i = 0; i < gs.length; i++) {
234 if (prevCallbacks[i] && prevCallbacks[i].unhighlightCallback) {
235 prevCallbacks[i].unhighlightCallback.apply(this, arguments);
239 gs[i].clearSelection();
243 }, true /* no need to redraw */);
247 Dygraph.synchronize = synchronize;