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 objet as each parameter, you may also pass an
26 * var sync = Dygraph.synchronize([g1, g2, g3], {
32 /* global Dygraph:false */
35 Dygraph
.synchronize
= function(/* dygraphs..., opts */) {
36 if (arguments
.length
=== 0) {
37 throw 'Invalid invocation of Dygraph.synchronize(). Need >= 1 argument.';
40 var OPTIONS
= ['selection', 'zoom', 'syncRange'];
48 var parseOpts
= function(obj
) {
49 if (!(obj
instanceof Object
)) {
50 throw 'Last argument must be either Dygraph or Object.';
52 for (var i
= 0; i
< OPTIONS
.length
; i
++) {
53 var optName
= OPTIONS
[i
];
54 if (obj
.hasOwnProperty(optName
)) opts
[optName
] = obj
[optName
];
59 if (arguments
[0] instanceof Dygraph
) {
60 // Arguments are Dygraph objects.
61 for (var i
= 0; i
< arguments
.length
; i
++) {
62 if (arguments
[i
] instanceof Dygraph
) {
63 dygraphs
.push(arguments
[i
]);
68 if (i
< arguments
.length
- 1) {
69 throw 'Invalid invocation of Dygraph.synchronize(). ' +
70 'All but the last argument must be Dygraph objects.';
71 } else if (i
== arguments
.length
- 1) {
72 parseOpts(arguments
[arguments
.length
- 1]);
74 } else if (arguments
[0].length
) {
75 // Invoked w/ list of dygraphs
, options
76 for (var i
= 0; i
< arguments
[0].length
; i
++) {
77 dygraphs
.push(arguments
[0][i
]);
79 if (arguments
.length
== 2) {
80 parseOpts(arguments
[1]);
81 } else if (arguments
.length
> 2) {
82 throw 'Invalid invocation of Dygraph.synchronize(). ' +
83 'Expected two arguments: array and optional options argument.';
84 } // otherwise arguments.length == 1, which is fine.
86 throw 'Invalid invocation of Dygraph.synchronize(). ' +
87 'First parameter must be either Dygraph or list of Dygraphs.';
90 if (dygraphs
.length
< 2) {
91 throw 'Invalid invocation of Dygraph.synchronize(). ' +
92 'Need two or more dygraphs to synchronize.';
95 // Listen for draw, highlight, unhighlight callbacks.
98 attachZoomHandlers(dygraphs
,true);
101 attachZoomHandlers(dygraphs
,false);
105 if (opts
.selection
) {
106 attachSelectionHandlers(dygraphs
);
111 for (var i
= 0; i
< dygraphs
.length
; i
++) {
114 g
.updateOptions({drawCallback
: null});
116 if (opts
.selection
) {
118 highlightCallback
: null,
119 unhighlightCallback
: null
123 // release references & make subsequent calls throw.
130 // TODO: call any `drawCallback`s that were set before this.
131 function attachZoomHandlers(gs
,syncRange
) {
133 for (var i
= 0; i
< gs
.length
; i
++) {
136 drawCallback
: function(me
, initial
) {
137 if (block
|| initial
) return;
139 var range
= me
.xAxisRange();
140 var yrange
= me
.yAxisRange();
141 for (var j
= 0; j
< gs
.length
; j
++) {
142 if (gs
[j
] == me
) continue;
144 gs
[j
].updateOptions( {
150 gs
[j
].updateOptions( {
157 }, false /* no need to redraw */);
161 function attachSelectionHandlers(gs
) {
163 for (var i
= 0; i
< gs
.length
; i
++) {
166 highlightCallback
: function(event
, x
, points
, row
, seriesName
) {
170 for (var i
= 0; i
< gs
.length
; i
++) {
171 if (me
== gs
[i
]) continue;
172 var idx
= dygraphsBinarySearch(gs
[i
], x
);
174 gs
[i
].setSelection(idx
, seriesName
);
179 unhighlightCallback
: function(event
) {
183 for (var i
= 0; i
< gs
.length
; i
++) {
184 if (me
== gs
[i
]) continue;
185 gs
[i
].clearSelection();
193 // Returns the index corresponding to xVal, or null if there is none.
194 function dygraphsBinarySearch(g
, xVal
) {
196 high
= g
.numRows() - 1;
199 var idx
= (high
+ low
) >> 1;
200 var x
= g
.getValue(idx
, 0);
203 } else if (x
> xVal
) {
210 // TODO: give an option to find the closest point, i.e. not demand an exact match.