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 */
38 Dygraph
.synchronize
= function(/* dygraphs..., opts */) {
39 if (arguments
.length
=== 0) {
40 throw 'Invalid invocation of Dygraph.synchronize(). Need >= 1 argument.';
43 var OPTIONS
= ['selection', 'zoom', 'range'];
57 var parseOpts
= function(obj
) {
58 if (!(obj
instanceof Object
)) {
59 throw 'Last argument must be either Dygraph or Object.';
61 for (var i
= 0; i
< OPTIONS
.length
; i
++) {
62 var optName
= OPTIONS
[i
];
63 if (obj
.hasOwnProperty(optName
)) opts
[optName
] = obj
[optName
];
68 if (arguments
[0] instanceof Dygraph
) {
69 // Arguments are Dygraph objects.
70 for (var i
= 0; i
< arguments
.length
; i
++) {
71 if (arguments
[i
] instanceof Dygraph
) {
72 dygraphs
.push(arguments
[i
]);
77 if (i
< arguments
.length
- 1) {
78 throw 'Invalid invocation of Dygraph.synchronize(). ' +
79 'All but the last argument must be Dygraph objects.';
80 } else if (i
== arguments
.length
- 1) {
81 parseOpts(arguments
[arguments
.length
- 1]);
83 } else if (arguments
[0].length
) {
84 // Invoked w/ list of dygraphs
, options
85 for (var i
= 0; i
< arguments
[0].length
; i
++) {
86 dygraphs
.push(arguments
[0][i
]);
88 if (arguments
.length
== 2) {
89 parseOpts(arguments
[1]);
90 } else if (arguments
.length
> 2) {
91 throw 'Invalid invocation of Dygraph.synchronize(). ' +
92 'Expected two arguments: array and optional options argument.';
93 } // otherwise arguments.length == 1, which is fine.
95 throw 'Invalid invocation of Dygraph.synchronize(). ' +
96 'First parameter must be either Dygraph or list of Dygraphs.';
99 if (dygraphs
.length
< 2) {
100 throw 'Invalid invocation of Dygraph.synchronize(). ' +
101 'Need two or more dygraphs to synchronize.';
104 // Listen for draw, highlight, unhighlight callbacks.
106 attachZoomHandlers(dygraphs
, opts
, prevCallbacks
);
109 if (opts
.selection
) {
110 attachSelectionHandlers(dygraphs
, prevCallbacks
);
115 for (var i
= 0; i
< dygraphs
.length
; i
++) {
118 g
.updateOptions({drawCallback
: prevCallbacks
.draw
});
120 if (opts
.selection
) {
122 highlightCallback
: prevCallbacks
.highlight
,
123 unhighlightCallback
: prevCallbacks
.unhighlight
127 // release references & make subsequent calls throw.
130 prevCallbacks
= null;
135 function attachZoomHandlers(gs
, syncOpts
, prevCallbacks
) {
137 for (var i
= 0; i
< gs
.length
; i
++) {
139 prevCallbacks
.draw
= g
.getFunctionOption('drawCallback');
141 drawCallback
: function(me
, initial
) {
142 if (prevCallbacks
.draw
) prevCallbacks
.draw(me
, initial
);
143 if (block
|| initial
) return;
146 dateWindow
: me
.xAxisRange()
148 if (syncOpts
.range
) opts
.valueRange
= me
.yAxisRange();
150 for (var j
= 0; j
< gs
.length
; j
++) {
151 if (gs
[j
] == me
) continue;
152 gs
[j
].updateOptions(opts
);
156 }, false /* no need to redraw */);
160 function attachSelectionHandlers(gs
, prevCallbacks
) {
162 for (var i
= 0; i
< gs
.length
; i
++) {
164 prevCallbacks
.highlight
= g
.getFunctionOption('highlightCallback');
165 prevCallbacks
.unhighlight
= g
.getFunctionOption('unhighlightCallback');
167 highlightCallback
: function(event
, x
, points
, row
, seriesName
) {
168 if (prevCallbacks
.highlight
) {
169 prevCallbacks
.highlight(event
, x
, points
, row
, seriesName
);
174 for (var i
= 0; i
< gs
.length
; i
++) {
175 if (me
== gs
[i
]) continue;
176 var idx
= dygraphsBinarySearch(gs
[i
], x
);
178 gs
[i
].setSelection(idx
, seriesName
);
183 unhighlightCallback
: function(event
) {
184 if (prevCallbacks
.unhighlight
) prevCallbacks
.unhighlight(event
);
188 for (var i
= 0; i
< gs
.length
; i
++) {
189 if (me
== gs
[i
]) continue;
190 gs
[i
].clearSelection();
198 // Returns the index corresponding to xVal, or null if there is none.
199 function dygraphsBinarySearch(g
, xVal
) {
201 high
= g
.numRows() - 1;
204 var idx
= (high
+ low
) >> 1;
205 var x
= g
.getValue(idx
, 0);
208 } else if (x
> xVal
) {
215 // TODO: give an option to find the closest point, i.e. not demand an exact match.