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).
35 /* global Dygraph:false */
40 Dygraph
= window
.Dygraph
;
41 } else if (typeof(module
) !== 'undefined') {
42 Dygraph
= require('../dygraph');
45 var synchronize
= function(/* dygraphs..., opts */) {
46 if (arguments
.length
=== 0) {
47 throw 'Invalid invocation of Dygraph.synchronize(). Need >= 1 argument.';
50 var OPTIONS
= ['selection', 'zoom', 'range'];
57 var prevCallbacks
= [];
59 var parseOpts
= function(obj
) {
60 if (!(obj
instanceof Object
)) {
61 throw 'Last argument must be either Dygraph or Object.';
63 for (var i
= 0; i
< OPTIONS
.length
; i
++) {
64 var optName
= OPTIONS
[i
];
65 if (obj
.hasOwnProperty(optName
)) opts
[optName
] = obj
[optName
];
70 if (arguments
[0] instanceof Dygraph
) {
71 // Arguments are Dygraph objects.
72 for (var i
= 0; i
< arguments
.length
; i
++) {
73 if (arguments
[i
] instanceof Dygraph
) {
74 dygraphs
.push(arguments
[i
]);
79 if (i
< arguments
.length
- 1) {
80 throw 'Invalid invocation of Dygraph.synchronize(). ' +
81 'All but the last argument must be Dygraph objects.';
82 } else if (i
== arguments
.length
- 1) {
83 parseOpts(arguments
[arguments
.length
- 1]);
85 } else if (arguments
[0].length
) {
86 // Invoked w/ list of dygraphs
, options
87 for (var i
= 0; i
< arguments
[0].length
; i
++) {
88 dygraphs
.push(arguments
[0][i
]);
90 if (arguments
.length
== 2) {
91 parseOpts(arguments
[1]);
92 } else if (arguments
.length
> 2) {
93 throw 'Invalid invocation of Dygraph.synchronize(). ' +
94 'Expected two arguments: array and optional options argument.';
95 } // otherwise arguments.length == 1, which is fine.
97 throw 'Invalid invocation of Dygraph.synchronize(). ' +
98 'First parameter must be either Dygraph or list of Dygraphs.';
101 if (dygraphs
.length
< 2) {
102 throw 'Invalid invocation of Dygraph.synchronize(). ' +
103 'Need two or more dygraphs to synchronize.';
106 var readycount
= dygraphs
.length
;
107 for (var i
= 0; i
< dygraphs
.length
; i
++) {
109 g
.ready( function() {
110 if (--readycount
== 0) {
111 // store original callbacks
112 var callBackTypes
= ['drawCallback', 'highlightCallback', 'unhighlightCallback'];
113 for (var j
= 0; j
< dygraphs
.length
; j
++) {
114 if (!prevCallbacks
[j
]) {
115 prevCallbacks
[j
] = {};
117 for (var k
= callBackTypes
.length
- 1; k
>= 0; k
--) {
118 prevCallbacks
[j
][callBackTypes
[k
]] = dygraphs
[j
].getFunctionOption(callBackTypes
[k
]);
122 // Listen for draw, highlight, unhighlight callbacks.
124 attachZoomHandlers(dygraphs
, opts
, prevCallbacks
);
127 if (opts
.selection
) {
128 attachSelectionHandlers(dygraphs
, prevCallbacks
);
136 for (var i
= 0; i
< dygraphs
.length
; i
++) {
139 g
.updateOptions({drawCallback
: prevCallbacks
[i
].drawCallback
});
141 if (opts
.selection
) {
143 highlightCallback
: prevCallbacks
[i
].highlightCallback
,
144 unhighlightCallback
: prevCallbacks
[i
].unhighlightCallback
148 // release references & make subsequent calls throw.
151 prevCallbacks
= null;
156 function arraysAreEqual(a
, b
) {
157 if (!Array
.isArray(a
) || !Array
.isArray(b
)) return false;
159 if (i
!== b
.length
) return false;
161 if (a
[i
] !== b
[i
]) return false;
166 function attachZoomHandlers(gs
, syncOpts
, prevCallbacks
) {
168 for (var i
= 0; i
< gs
.length
; i
++) {
171 drawCallback
: function(me
, initial
) {
172 if (block
|| initial
) return;
175 dateWindow
: me
.xAxisRange()
177 if (syncOpts
.range
) opts
.valueRange
= me
.yAxisRange();
179 for (var j
= 0; j
< gs
.length
; j
++) {
181 if (prevCallbacks
[j
] && prevCallbacks
[j
].drawCallback
) {
182 prevCallbacks
[j
].drawCallback
.apply(this, arguments
);
187 // Only redraw if there are new options
188 if (arraysAreEqual(opts
.dateWindow
, gs
[j
].getOption('dateWindow')) &&
189 arraysAreEqual(opts
.valueRange
, gs
[j
].getOption('valueRange'))) {
193 gs
[j
].updateOptions(opts
);
197 }, true /* no need to redraw */);
201 function attachSelectionHandlers(gs
, prevCallbacks
) {
203 for (var i
= 0; i
< gs
.length
; i
++) {
207 highlightCallback
: function(event
, x
, points
, row
, seriesName
) {
211 for (var i
= 0; i
< gs
.length
; i
++) {
213 if (prevCallbacks
[i
] && prevCallbacks
[i
].highlightCallback
) {
214 prevCallbacks
[i
].highlightCallback
.apply(this, arguments
);
218 var idx
= gs
[i
].getRowForX(x
);
220 gs
[i
].setSelection(idx
, seriesName
);
225 unhighlightCallback
: function(event
) {
229 for (var i
= 0; i
< gs
.length
; i
++) {
231 if (prevCallbacks
[i
] && prevCallbacks
[i
].unhighlightCallback
) {
232 prevCallbacks
[i
].unhighlightCallback
.apply(this, arguments
);
236 gs
[i
].clearSelection();
240 }, true /* no need to redraw */);
244 Dygraph
.synchronize
= synchronize
;