1 // Copyright (c) 2009 Dan Vanderkam. All rights reserved.
3 // SPDX-License-Identifier: MIT
6 * Synchronize zooming and/or selections between a set of dygraphs.
10 * var g1 = new Dygraph(...),
11 * g2 = new Dygraph(...),
13 * var sync = Dygraph.synchronize(g1, g2, ...);
14 * // charts are now synchronized
16 * // charts are no longer synchronized
18 * You can set options using the last parameter, for example:
20 * var sync = Dygraph.synchronize(g1, g2, g3, {
25 * The default is to synchronize both of these.
27 * Instead of passing one Dygraph object as each parameter, you may also pass an
30 * var sync = Dygraph.synchronize([g1, g2, g3], {
35 * You may also set `range: false` if you wish to only sync the x-axis.
36 * The `range` option has no effect unless `zoom` is true (the default).
38 * Original source: https://github.com/danvk/dygraphs/blob/master/src/extras/synchronizer.js
39 * at commit b55a71d768d2f8de62877c32b3aec9e9975ac389
41 * Copyright (c) 2009 Dan Vanderkam
43 * Permission is hereby granted, free of charge, to any person
44 * obtaining a copy of this software and associated documentation
45 * files (the "Software"), to deal in the Software without
46 * restriction, including without limitation the rights to use,
47 * copy, modify, merge, publish, distribute, sublicense, and/or sell
48 * copies of the Software, and to permit persons to whom the
49 * Software is furnished to do so, subject to the following
52 * The above copyright notice and this permission notice shall be
53 * included in all copies or substantial portions of the Software.
55 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
56 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
57 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
58 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
59 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
60 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
61 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
62 * OTHER DEALINGS IN THE SOFTWARE.
65 /* global Dygraph:false */
70 Dygraph = window.Dygraph;
71 } else if (typeof(module) !== 'undefined') {
72 Dygraph = require('../dygraph');
75 var synchronize = function(/* dygraphs..., opts */) {
76 if (arguments.length === 0) {
77 throw 'Invalid invocation of Dygraph.synchronize(). Need >= 1 argument.';
80 var OPTIONS = ['selection', 'zoom', 'range'];
87 var prevCallbacks = [];
89 var parseOpts = function(obj) {
90 if (!(obj instanceof Object)) {
91 throw 'Last argument must be either Dygraph or Object.';
93 for (var i = 0; i < OPTIONS.length; i++) {
94 var optName = OPTIONS[i];
95 if (obj.hasOwnProperty(optName)) opts[optName] = obj[optName];
100 if (arguments[0] instanceof Dygraph) {
101 // Arguments are Dygraph objects.
102 for (var i = 0; i < arguments.length; i++) {
103 if (arguments[i] instanceof Dygraph) {
104 dygraphs.push(arguments[i]);
109 if (i < arguments.length - 1) {
110 throw 'Invalid invocation of Dygraph.synchronize(). ' +
111 'All but the last argument must be Dygraph objects.';
112 } else if (i == arguments.length - 1) {
113 parseOpts(arguments[arguments.length - 1]);
115 } else if (arguments[0].length) {
116 // Invoked w/ list of dygraphs, options
117 for (var i = 0; i < arguments[0].length; i++) {
118 dygraphs.push(arguments[0][i]);
120 if (arguments.length == 2) {
121 parseOpts(arguments[1]);
122 } else if (arguments.length > 2) {
123 throw 'Invalid invocation of Dygraph.synchronize(). ' +
124 'Expected two arguments: array and optional options argument.';
125 } // otherwise arguments.length == 1, which is fine.
127 throw 'Invalid invocation of Dygraph.synchronize(). ' +
128 'First parameter must be either Dygraph or list of Dygraphs.';
131 if (dygraphs.length < 2) {
132 throw 'Invalid invocation of Dygraph.synchronize(). ' +
133 'Need two or more dygraphs to synchronize.';
136 var readycount = dygraphs.length;
137 for (var i = 0; i < dygraphs.length; i++) {
139 g.ready( function() {
140 if (--readycount == 0) {
141 // store original callbacks
142 var callBackTypes = ['drawCallback', 'highlightCallback', 'unhighlightCallback'];
143 for (var j = 0; j < dygraphs.length; j++) {
144 if (!prevCallbacks[j]) {
145 prevCallbacks[j] = {};
147 for (var k = callBackTypes.length - 1; k >= 0; k--) {
148 prevCallbacks[j][callBackTypes[k]] = dygraphs[j].getFunctionOption(callBackTypes[k]);
152 // Listen for draw, highlight, unhighlight callbacks.
154 attachZoomHandlers(dygraphs, opts, prevCallbacks);
157 if (opts.selection) {
158 attachSelectionHandlers(dygraphs, prevCallbacks);
166 for (var i = 0; i < dygraphs.length; i++) {
169 g.updateOptions({drawCallback: prevCallbacks[i].drawCallback});
171 if (opts.selection) {
173 highlightCallback: prevCallbacks[i].highlightCallback,
174 unhighlightCallback: prevCallbacks[i].unhighlightCallback
178 // release references & make subsequent calls throw.
181 prevCallbacks = null;
186 function arraysAreEqual(a, b) {
187 if (!Array.isArray(a) || !Array.isArray(b)) return false;
189 if (i !== b.length) return false;
191 if (a[i] !== b[i]) return false;
196 function attachZoomHandlers(gs, syncOpts, prevCallbacks) {
198 for (var i = 0; i < gs.length; i++) {
201 drawCallback: function(me, initial) {
202 if (block || initial) return;
205 dateWindow: me.xAxisRange()
207 if (syncOpts.range) opts.valueRange = me.yAxisRange();
209 for (var j = 0; j < gs.length; j++) {
211 if (prevCallbacks[j] && prevCallbacks[j].drawCallback) {
212 prevCallbacks[j].drawCallback.apply(this, arguments);
217 // Only redraw if there are new options
218 if (arraysAreEqual(opts.dateWindow, gs[j].getOption('dateWindow')) &&
219 arraysAreEqual(opts.valueRange, gs[j].getOption('valueRange'))) {
223 gs[j].updateOptions(opts);
227 }, true /* no need to redraw */);
231 function attachSelectionHandlers(gs, prevCallbacks) {
233 for (var i = 0; i < gs.length; i++) {
237 highlightCallback: function(event, x, points, row, seriesName) {
241 for (var i = 0; i < gs.length; i++) {
243 if (prevCallbacks[i] && prevCallbacks[i].highlightCallback) {
244 prevCallbacks[i].highlightCallback.apply(this, arguments);
248 var idx = gs[i].getRowForX(x);
250 gs[i].setSelection(idx, seriesName);
255 unhighlightCallback: function(event) {
259 for (var i = 0; i < gs.length; i++) {
261 if (prevCallbacks[i] && prevCallbacks[i].unhighlightCallback) {
262 prevCallbacks[i].unhighlightCallback.apply(this, arguments);
266 gs[i].clearSelection();
270 }, true /* no need to redraw */);
274 Dygraph.synchronize = synchronize;