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'];
47 var parseOpts
= function(obj
) {
48 if (!(obj
instanceof Object
)) {
49 throw 'Last argument must be either Dygraph or Object.';
51 for (var i
= 0; i
< OPTIONS
.length
; i
++) {
52 var optName
= OPTIONS
[i
];
53 if (obj
.hasOwnProperty(optName
)) opts
[optName
] = obj
[optName
];
58 if (arguments
[0] instanceof Dygraph
) {
59 // Arguments are Dygraph objects.
60 for (var i
= 0; i
< arguments
.length
; i
++) {
61 if (arguments
[i
] instanceof Dygraph
) {
62 dygraphs
.push(arguments
[i
]);
67 if (i
< arguments
.length
- 1) {
68 throw 'Invalid invocation of Dygraph.synchronize(). ' +
69 'All but the last argument must be Dygraph objects.';
70 } else if (i
== arguments
.length
- 1) {
71 parseOpts(arguments
[arguments
.length
- 1]);
73 } else if (arguments
[0].length
) {
74 // Invoked w/ list of dygraphs
, options
75 for (var i
= 0; i
< arguments
[0].length
; i
++) {
76 dygraphs
.push(arguments
[0][i
]);
78 if (arguments
.length
== 2) {
79 parseOpts(arguments
[1]);
80 } else if (arguments
.length
> 2) {
81 throw 'Invalid invocation of Dygraph.synchronize(). ' +
82 'Expected two arguments: array and optional options argument.';
83 } // otherwise arguments.length == 1, which is fine.
85 throw 'Invalid invocation of Dygraph.synchronize(). ' +
86 'First parameter must be either Dygraph or list of Dygraphs.';
89 if (dygraphs
.length
< 2) {
90 throw 'Invalid invocation of Dygraph.synchronize(). ' +
91 'Need two or more dygraphs to synchronize.';
94 // Listen for draw, highlight, unhighlight callbacks.
96 attachZoomHandlers(dygraphs
);
100 attachSelectionHandlers(dygraphs
);
105 for (var i
= 0; i
< dygraphs
.length
; i
++) {
108 g
.updateOptions({drawCallback
: null});
110 if (opts
.selection
) {
112 highlightCallback
: null,
113 unhighlightCallback
: null
117 // release references & make subsequent calls throw.
124 // TODO: call any `drawCallback`s that were set before this.
125 function attachZoomHandlers(gs
) {
127 for (var i
= 0; i
< gs
.length
; i
++) {
130 drawCallback
: function(me
, initial
) {
131 if (block
|| initial
) return;
133 var range
= me
.xAxisRange();
134 var yrange
= me
.yAxisRange();
135 for (var j
= 0; j
< gs
.length
; j
++) {
136 if (gs
[j
] == me
) continue;
137 gs
[j
].updateOptions( {
144 }, false /* no need to redraw */);
148 function attachSelectionHandlers(gs
) {
150 for (var i
= 0; i
< gs
.length
; i
++) {
153 highlightCallback
: function(event
, x
, points
, row
, seriesName
) {
157 for (var i
= 0; i
< gs
.length
; i
++) {
158 if (me
== gs
[i
]) continue;
159 var idx
= dygraphsBinarySearch(gs
[i
], x
);
161 gs
[i
].setSelection(idx
, seriesName
);
166 unhighlightCallback
: function(event
) {
170 for (var i
= 0; i
< gs
.length
; i
++) {
171 if (me
== gs
[i
]) continue;
172 gs
[i
].clearSelection();
180 // Returns the index corresponding to xVal, or null if there is none.
181 function dygraphsBinarySearch(g
, xVal
) {
183 high
= g
.numRows() - 1;
186 var idx
= (high
+ low
) >> 1;
187 var x
= g
.getValue(idx
, 0);
190 } else if (x
> xVal
) {
197 // TODO: give an option to find the closest point, i.e. not demand an exact match.