]> arthur.barton.de Git - netdata.git/blob - web/lib/dygraph-synchronizer.js
minor code cleanups; updated the dashboard to fix inline charts
[netdata.git] / web / lib / dygraph-synchronizer.js
1 /**
2  * Synchronize zooming and/or selections between a set of dygraphs.
3  *
4  * Usage:
5  *
6  *   var g1 = new Dygraph(...),
7  *       g2 = new Dygraph(...),
8  *       ...;
9  *   var sync = Dygraph.synchronize(g1, g2, ...);
10  *   // charts are now synchronized
11  *   sync.detach();
12  *   // charts are no longer synchronized
13  *
14  * You can set options using the last parameter, for example:
15  *
16  *   var sync = Dygraph.synchronize(g1, g2, g3, {
17  *      selection: true,
18  *      zoom: true
19  *   });
20  *
21  * The default is to synchronize both of these.
22  *
23  * Instead of passing one Dygraph object as each parameter, you may also pass an
24  * array of dygraphs:
25  *
26  *   var sync = Dygraph.synchronize([g1, g2, g3], {
27  *      selection: false,
28  *      zoom: true
29  *   });
30  *
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).
33  */
34 (function() {
35 /* global Dygraph:false */
36 'use strict';
37
38 var Dygraph;
39 if (window.Dygraph) {
40   Dygraph = window.Dygraph;
41 } else if (typeof(module) !== 'undefined') {
42   Dygraph = require('../dygraph');
43 }
44
45 var synchronize = function(/* dygraphs..., opts */) {
46   if (arguments.length === 0) {
47     throw 'Invalid invocation of Dygraph.synchronize(). Need >= 1 argument.';
48   }
49
50   var OPTIONS = ['selection', 'zoom', 'range'];
51   var opts = {
52     selection: true,
53     zoom: true,
54     range: true
55   };
56   var dygraphs = [];
57   var prevCallbacks = [];
58
59   var parseOpts = function(obj) {
60     if (!(obj instanceof Object)) {
61       throw 'Last argument must be either Dygraph or Object.';
62     } else {
63       for (var i = 0; i < OPTIONS.length; i++) {
64         var optName = OPTIONS[i];
65         if (obj.hasOwnProperty(optName)) opts[optName] = obj[optName];
66       }
67     }
68   };
69
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]);
75       } else {
76         break;
77       }
78     }
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]);
84     }
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]);
89     }
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.
96   } else {
97     throw 'Invalid invocation of Dygraph.synchronize(). ' +
98           'First parameter must be either Dygraph or list of Dygraphs.';
99   }
100
101   if (dygraphs.length < 2) {
102     throw 'Invalid invocation of Dygraph.synchronize(). ' +
103           'Need two or more dygraphs to synchronize.';
104   }
105
106   var readycount = dygraphs.length;
107   for (var i = 0; i < dygraphs.length; i++) {
108     var g = dygraphs[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] = {};
116           }
117           for (var k = callBackTypes.length - 1; k >= 0; k--) {
118             prevCallbacks[j][callBackTypes[k]] = dygraphs[j].getFunctionOption(callBackTypes[k]);
119           }
120         }
121
122         // Listen for draw, highlight, unhighlight callbacks.
123         if (opts.zoom) {
124           attachZoomHandlers(dygraphs, opts, prevCallbacks);
125         }
126
127         if (opts.selection) {
128           attachSelectionHandlers(dygraphs, prevCallbacks);
129         }
130       }
131     });
132   }
133
134   return {
135     detach: function() {
136       for (var i = 0; i < dygraphs.length; i++) {
137         var g = dygraphs[i];
138         if (opts.zoom) {
139           g.updateOptions({drawCallback: prevCallbacks[i].drawCallback});
140         }
141         if (opts.selection) {
142           g.updateOptions({
143             highlightCallback: prevCallbacks[i].highlightCallback,
144             unhighlightCallback: prevCallbacks[i].unhighlightCallback
145           });
146         }
147       }
148       // release references & make subsequent calls throw.
149       dygraphs = null;
150       opts = null;
151       prevCallbacks = null;
152     }
153   };
154 };
155
156 function attachZoomHandlers(gs, syncOpts, prevCallbacks) {
157   var block = false;
158   for (var i = 0; i < gs.length; i++) {
159     var g = gs[i];
160     g.updateOptions({
161       drawCallback: function(me, initial) {
162         if (block || initial) return;
163         block = true;
164         var opts = {
165           dateWindow: me.xAxisRange()
166         };
167         if (syncOpts.range) opts.valueRange = me.yAxisRange();
168
169         for (var j = 0; j < gs.length; j++) {
170           if (gs[j] == me) {
171             if (prevCallbacks[j] && prevCallbacks[j].drawCallback) {
172               prevCallbacks[j].drawCallback.apply(this, arguments);
173             }
174             continue;
175           }
176           gs[j].updateOptions(opts);
177         }
178         block = false;
179       }
180     }, true /* no need to redraw */);
181   }
182 }
183
184 function attachSelectionHandlers(gs, prevCallbacks) {
185   var block = false;
186   for (var i = 0; i < gs.length; i++) {
187     var g = gs[i];
188
189     g.updateOptions({
190       highlightCallback: function(event, x, points, row, seriesName) {
191         if (block) return;
192         block = true;
193         var me = this;
194         for (var i = 0; i < gs.length; i++) {
195           if (me == gs[i]) {
196             if (prevCallbacks[i] && prevCallbacks[i].highlightCallback) {
197               prevCallbacks[i].highlightCallback.apply(this, arguments);
198             }
199             continue;
200           }
201           var idx = gs[i].getRowForX(x);
202           if (idx !== null) {
203             gs[i].setSelection(idx, seriesName);
204           }
205         }
206         block = false;
207       },
208       unhighlightCallback: function(event) {
209         if (block) return;
210         block = true;
211         var me = this;
212         for (var i = 0; i < gs.length; i++) {
213           if (me == gs[i]) {
214             if (prevCallbacks[i] && prevCallbacks[i].unhighlightCallback) {
215               prevCallbacks[i].unhighlightCallback.apply(this, arguments);
216             }
217             continue;
218           }
219           gs[i].clearSelection();
220         }
221         block = false;
222       }
223     }, true /* no need to redraw */);
224   }
225 }
226
227 Dygraph.synchronize = synchronize;
228
229 })();