]> arthur.barton.de Git - netdata.git/blob - web/dashboard.js
3b8f5989e76355bd9a4a2b1000bb02baf3519868
[netdata.git] / web / dashboard.js
1 // ----------------------------------------------------------------------------
2 // You can set the following variables before loading this script:
3
4 /*global netdataNoDygraphs       *//* boolean,  disable dygraph charts
5  *                                              (default: false) */
6 /*global netdataNoSparklines     *//* boolean,  disable sparkline charts
7  *                                              (default: false) */
8 /*global netdataNoPeitys         *//* boolean,  disable peity charts
9  *                                              (default: false) */
10 /*global netdataNoGoogleCharts   *//* boolean,  disable google charts
11  *                                              (default: false) */
12 /*global netdataNoMorris         *//* boolean,  disable morris charts
13  *                                              (default: false) */
14 /*global netdataNoEasyPieChart   *//* boolean,  disable easypiechart charts
15  *                                              (default: false) */
16 /*global netdataNoGauge          *//* boolean,  disable gauge.js charts
17  *                                              (default: false) */
18 /*global netdataNoD3             *//* boolean,  disable d3 charts
19  *                                              (default: false) */
20 /*global netdataNoC3             *//* boolean,  disable c3 charts
21  *                                              (default: false) */
22 /*global netdataNoBootstrap      *//* boolean,  disable bootstrap - disables help too
23  *                                              (default: false) */
24 /*global netdataDontStart        *//* boolean,  do not start the thread to process the charts
25  *                                              (default: false) */
26 /*global netdataErrorCallback    *//* function, callback to be called when the dashboard encounters an error
27  *                                              (default: null) */
28 /*global netdataRegistry         *//* boolean,  use the netdata registry
29  *                                              (default: false) */
30 /*global netdataNoRegistry       *//* boolean,  included only for compatibility with existing custom dashboard
31  *                                              (obsolete - do not use this any more) */
32 /*global netdataRegistryCallback *//* function, callback that will be invoked with one param: the URLs from the registry
33  *                                              (default: null) */
34 /*global netdataShowHelp         *//* boolean,  disable charts help
35  *                                              (default: true) */
36 /*global netdataShowAlarms       *//* boolean,  enable alarms checks and notifications
37  *                                              (default: false) */
38 /*global netdataRegistryAfterMs  *//* ms,       delay registry use at started
39  *                                              (default: 1500) */
40 /*global netdataCallback         *//* function, callback to be called when netdata is ready to start
41  *                                              (default: null)
42  *                                              netdata will be running while this is called
43  *                                              (call NETDATA.pause to stop it) */
44 /*global netdataPrepCallback     *//* function, callback to be called before netdata does anything else
45  *                                              (default: null) */
46 /*global netdataServer           *//* string,   the URL of the netdata server to use
47  *                                              (default: the URL the page is hosted at) */
48
49 // ----------------------------------------------------------------------------
50 // global namespace
51
52 var NETDATA = window.NETDATA || {};
53
54 (function(window, document) {
55     // ------------------------------------------------------------------------
56     // compatibility fixes
57
58     // fix IE issue with console
59     if(!window.console) { window.console = { log: function(){} }; }
60
61     // if string.endsWith is not defined, define it
62     if(typeof String.prototype.endsWith !== 'function') {
63         String.prototype.endsWith = function(s) {
64             if(s.length > this.length) return false;
65             return this.slice(-s.length) === s;
66         };
67     }
68
69     // if string.startsWith is not defined, define it
70     if(typeof String.prototype.startsWith !== 'function') {
71         String.prototype.startsWith = function(s) {
72             if(s.length > this.length) return false;
73             return this.slice(s.length) === s;
74         };
75     }
76
77     NETDATA.name2id = function(s) {
78         return s
79             .replace(/ /g, '_')
80             .replace(/\(/g, '_')
81             .replace(/\)/g, '_')
82             .replace(/\./g, '_')
83             .replace(/\//g, '_');
84     };
85
86     // ----------------------------------------------------------------------------------------------------------------
87     // Detect the netdata server
88
89     // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
90     // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
91     NETDATA._scriptSource = function() {
92         var script = null;
93
94         if(typeof document.currentScript !== 'undefined') {
95             script = document.currentScript;
96         }
97         else {
98             var all_scripts = document.getElementsByTagName('script');
99             script = all_scripts[all_scripts.length - 1];
100         }
101
102         if (typeof script.getAttribute.length !== 'undefined')
103             script = script.src;
104         else
105             script = script.getAttribute('src', -1);
106
107         return script;
108     };
109
110     if(typeof netdataServer !== 'undefined')
111         NETDATA.serverDefault = netdataServer;
112     else {
113         var s = NETDATA._scriptSource();
114         if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
115         else {
116             console.log('WARNING: Cannot detect the URL of the netdata server.');
117             NETDATA.serverDefault = null;
118         }
119     }
120
121     if(NETDATA.serverDefault === null)
122         NETDATA.serverDefault = '';
123     else if(NETDATA.serverDefault.slice(-1) !== '/')
124         NETDATA.serverDefault += '/';
125
126     // default URLs for all the external files we need
127     // make them RELATIVE so that the whole thing can also be
128     // installed under a web server
129     NETDATA.jQuery              = NETDATA.serverDefault + 'lib/jquery-2.2.4.min.js';
130     NETDATA.peity_js            = NETDATA.serverDefault + 'lib/jquery.peity-3.2.0.min.js';
131     NETDATA.sparkline_js        = NETDATA.serverDefault + 'lib/jquery.sparkline-2.1.2.min.js';
132     NETDATA.easypiechart_js     = NETDATA.serverDefault + 'lib/jquery.easypiechart-97b5824.min.js';
133     NETDATA.gauge_js            = NETDATA.serverDefault + 'lib/gauge-d5260c3.min.js';
134     NETDATA.dygraph_js          = NETDATA.serverDefault + 'lib/dygraph-combined-dd74404.js';
135     NETDATA.dygraph_smooth_js   = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter-dd74404.js';
136     NETDATA.raphael_js          = NETDATA.serverDefault + 'lib/raphael-2.2.4-min.js';
137     NETDATA.c3_js               = NETDATA.serverDefault + 'lib/c3-0.4.11.min.js';
138     NETDATA.c3_css              = NETDATA.serverDefault + 'css/c3-0.4.11.min.css';
139     NETDATA.d3_js               = NETDATA.serverDefault + 'lib/d3-3.5.17.min.js';
140     NETDATA.morris_js           = NETDATA.serverDefault + 'lib/morris-0.5.1.min.js';
141     NETDATA.morris_css          = NETDATA.serverDefault + 'css/morris-0.5.1.css';
142     NETDATA.google_js           = 'https://www.google.com/jsapi';
143
144     NETDATA.themes = {
145         white: {
146             bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-3.3.7.css',
147             dashboard_css: NETDATA.serverDefault + 'dashboard.css?v20161229-2',
148             background: '#FFFFFF',
149             foreground: '#000000',
150             grid: '#F0F0F0',
151             axis: '#F0F0F0',
152             colors: [   '#3366CC', '#DC3912',   '#109618', '#FF9900',   '#990099', '#DD4477',
153                         '#3B3EAC', '#66AA00',   '#0099C6', '#B82E2E',   '#AAAA11', '#5574A6',
154                         '#994499', '#22AA99',   '#6633CC', '#E67300',   '#316395', '#8B0707',
155                         '#329262', '#3B3EAC' ],
156             easypiechart_track: '#f0f0f0',
157             easypiechart_scale: '#dfe0e0',
158             gauge_pointer: '#C0C0C0',
159             gauge_stroke: '#F0F0F0',
160             gauge_gradient: false
161         },
162         slate: {
163             bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-slate-flat-3.3.7.css?v20161229-1',
164             dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css?v20161229-2',
165             background: '#272b30',
166             foreground: '#C8C8C8',
167             grid: '#283236',
168             axis: '#283236',
169 /*          colors: [   '#55bb33', '#ff2222',   '#0099C6', '#faa11b',   '#adbce0', '#DDDD00',
170                         '#4178ba', '#f58122',   '#a5cc39', '#f58667',   '#f5ef89', '#cf93c0',
171                         '#a5d18a', '#b8539d',   '#3954a3', '#c8a9cf',   '#c7de8a', '#fad20a',
172                         '#a6a479', '#a66da8' ],
173 */
174             colors: [   '#66AA00', '#FE3912',   '#3366CC', '#D66300',   '#0099C6', '#DDDD00',
175                         '#5054e6', '#EE9911',   '#BB44CC', '#e45757',   '#ef0aef', '#CC7700',
176                         '#22AA99', '#109618',   '#905bfd', '#f54882',   '#4381bf', '#ff3737',
177                         '#329262', '#3B3EFF' ],
178             easypiechart_track: '#373b40',
179             easypiechart_scale: '#373b40',
180             gauge_pointer: '#474b50',
181             gauge_stroke: '#373b40',
182             gauge_gradient: false
183         }
184     };
185
186     if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
187         NETDATA.themes.current = NETDATA.themes[netdataTheme];
188     else
189         NETDATA.themes.current = NETDATA.themes.white;
190
191     NETDATA.colors = NETDATA.themes.current.colors;
192
193     // these are the colors Google Charts are using
194     // we have them here to attempt emulate their look and feel on the other chart libraries
195     // http://there4.io/2012/05/02/google-chart-color-list/
196     //NETDATA.colors        = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
197     //                      '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
198     //                      '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
199
200     // an alternative set
201     // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
202     //                         (blue)     (red)      (orange)   (green)    (pink)     (brown)    (purple)   (yellow)   (gray)
203     //NETDATA.colors        = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
204
205     if(typeof netdataShowHelp === 'undefined')
206         netdataShowHelp = true;
207
208     if(typeof netdataShowAlarms === 'undefined')
209         netdataShowAlarms = false;
210
211     if(typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0)
212         netdataRegistryAfterMs = 1500;
213
214     if(typeof netdataRegistry === 'undefined') {
215         // backward compatibility
216         netdataRegistry = (typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false);
217     }
218     if(netdataRegistry === false && typeof netdataRegistryCallback === 'function')
219         netdataRegistry = true;
220
221     // ----------------------------------------------------------------------------------------------------------------
222     // the defaults for all charts
223
224     // if the user does not specify any of these, the following will be used
225
226     NETDATA.chartDefaults = {
227         host: NETDATA.serverDefault,    // the server to get data from
228         width: '100%',                  // the chart width - can be null
229         height: '100%',                 // the chart height - can be null
230         min_width: null,                // the chart minimum width - can be null
231         library: 'dygraph',             // the graphing library to use
232         method: 'average',              // the grouping method
233         before: 0,                      // panning
234         after: -600,                    // panning
235         pixels_per_point: 1,            // the detail of the chart
236         fill_luminance: 0.8             // luminance of colors in solit areas
237     };
238
239     // ----------------------------------------------------------------------------------------------------------------
240     // global options
241
242     NETDATA.options = {
243         pauseCallback: null,            // a callback when we are really paused
244
245         pause: false,                   // when enabled we don't auto-refresh the charts
246
247         targets: null,                  // an array of all the state objects that are
248                                         // currently active (independently of their
249                                         // viewport visibility)
250
251         updated_dom: true,              // when true, the DOM has been updated with
252                                         // new elements we have to check.
253
254         auto_refresher_fast_weight: 0,  // this is the current time in ms, spent
255                                         // rendering charts continiously.
256                                         // used with .current.fast_render_timeframe
257
258         page_is_visible: true,          // when true, this page is visible
259
260         auto_refresher_stop_until: 0,   // timestamp in ms - used internaly, to stop the
261                                         // auto-refresher for some time (when a chart is
262                                         // performing pan or zoom, we need to stop refreshing
263                                         // all other charts, to have the maximum speed for
264                                         // rendering the chart that is panned or zoomed).
265                                         // Used with .current.global_pan_sync_time
266
267         last_resized: Date.now(),       // the timestamp of the last resize request
268
269         last_page_scroll: 0,            // the timestamp the last time the page was scrolled
270
271         // the current profile
272         // we may have many...
273         current: {
274             pixels_per_point: 1,        // the minimum pixels per point for all charts
275                                         // increase this to speed javascript up
276                                         // each chart library has its own limit too
277                                         // the max of this and the chart library is used
278                                         // the final is calculated every time, so a change
279                                         // here will have immediate effect on the next chart
280                                         // update
281
282             idle_between_charts: 100,   // ms - how much time to wait between chart updates
283
284             fast_render_timeframe: 200, // ms - render continously until this time of continious
285                                         // rendering has been reached
286                                         // this setting is used to make it render e.g. 10
287                                         // charts at once, sleep idle_between_charts time
288                                         // and continue for another 10 charts.
289
290             idle_between_loops: 500,    // ms - if all charts have been updated, wait this
291                                         // time before starting again.
292
293             idle_parallel_loops: 100,   // ms - the time between parallel refresher updates
294
295             idle_lost_focus: 500,       // ms - when the window does not have focus, check
296                                         // if focus has been regained, every this time
297
298             global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
299                                         // autorefreshing of charts is paused for this amount
300                                         // of time
301
302             sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
303                                         // of time before setting up synchronized selections
304                                         // on hover.
305
306             sync_selection: true,       // enable or disable selection sync
307
308             pan_and_zoom_delay: 50,     // when panning or zooming, how ofter to update the chart
309
310             sync_pan_and_zoom: true,    // enable or disable pan and zoom sync
311
312             pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
313
314             update_only_visible: true,  // enable or disable visibility management
315
316             parallel_refresher: true,   // enable parallel refresh of charts
317
318             concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
319
320             destroy_on_hide: false,     // destroy charts when they are not visible
321
322             show_help: netdataShowHelp, // when enabled the charts will show some help
323             show_help_delay_show_ms: 500,
324             show_help_delay_hide_ms: 0,
325
326             eliminate_zero_dimensions: true, // do not show dimensions with just zeros
327
328             stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
329             stop_updates_while_resizing: 1000,  // ms - time to stop auto-refreshes while resizing the charts
330
331             double_click_speed: 500,    // ms - time between clicks / taps to detect double click/tap
332
333             smooth_plot: true,          // enable smooth plot, where possible
334
335             charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
336
337             color_fill_opacity_line: 1.0,
338             color_fill_opacity_area: 0.2,
339             color_fill_opacity_stacked: 0.8,
340
341             pan_and_zoom_factor: 0.25,      // the increment when panning and zooming with the toolbox
342             pan_and_zoom_factor_multiplier_control: 2.0,
343             pan_and_zoom_factor_multiplier_shift: 3.0,
344             pan_and_zoom_factor_multiplier_alt: 4.0,
345
346             abort_ajax_on_scroll: false,            // kill pending ajax page scroll
347             async_on_scroll: false,                 // sync/async onscroll handler
348             onscroll_worker_duration_threshold: 30, // time in ms, to consider slow the onscroll handler
349
350             retries_on_data_failures: 3, // how many retries to make if we can't fetch chart data from the server
351
352             setOptionCallback: function() { }
353         },
354
355         debug: {
356             show_boxes:         false,
357             main_loop:          false,
358             focus:              false,
359             visibility:         false,
360             chart_data_url:     false,
361             chart_errors:       false, // FIXME: remember to set it to false before merging
362             chart_timing:       false,
363             chart_calls:        false,
364             libraries:          false,
365             dygraph:            false
366         }
367     };
368
369     NETDATA.statistics = {
370         refreshes_total: 0,
371         refreshes_active: 0,
372         refreshes_active_max: 0
373     };
374
375
376     // ----------------------------------------------------------------------------------------------------------------
377     // local storage options
378
379     NETDATA.localStorage = {
380         default: {},
381         current: {},
382         callback: {} // only used for resetting back to defaults
383     };
384
385     NETDATA.localStorageTested = -1;
386     NETDATA.localStorageTest = function() {
387         if(NETDATA.localStorageTested !== -1)
388             return NETDATA.localStorageTested;
389
390         if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
391             var test = 'test';
392             try {
393                 localStorage.setItem(test, test);
394                 localStorage.removeItem(test);
395                 NETDATA.localStorageTested = true;
396             }
397             catch (e) {
398                 NETDATA.localStorageTested = false;
399             }
400         }
401         else
402             NETDATA.localStorageTested = false;
403
404         return NETDATA.localStorageTested;
405     };
406
407     NETDATA.localStorageGet = function(key, def, callback) {
408         var ret = def;
409
410         if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
411             NETDATA.localStorage.default[key.toString()] = def;
412             NETDATA.localStorage.callback[key.toString()] = callback;
413         }
414
415         if(NETDATA.localStorageTest() === true) {
416             try {
417                 // console.log('localStorage: loading "' + key.toString() + '"');
418                 ret = localStorage.getItem(key.toString());
419                 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
420                 if(ret === null || ret === 'undefined') {
421                     // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
422                     localStorage.setItem(key.toString(), JSON.stringify(def));
423                     ret = def;
424                 }
425                 else {
426                     // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
427                     ret = JSON.parse(ret);
428                     // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
429                 }
430             }
431             catch(error) {
432                 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
433                 ret = def;
434             }
435         }
436
437         if(typeof ret === 'undefined' || ret === 'undefined') {
438             console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
439             ret = def;
440         }
441
442         NETDATA.localStorage.current[key.toString()] = ret;
443         return ret;
444     };
445
446     NETDATA.localStorageSet = function(key, value, callback) {
447         if(typeof value === 'undefined' || value === 'undefined') {
448             console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
449         }
450
451         if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
452             NETDATA.localStorage.default[key.toString()] = value;
453             NETDATA.localStorage.current[key.toString()] = value;
454             NETDATA.localStorage.callback[key.toString()] = callback;
455         }
456
457         if(NETDATA.localStorageTest() === true) {
458             // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
459             try {
460                 localStorage.setItem(key.toString(), JSON.stringify(value));
461             }
462             catch(e) {
463                 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
464             }
465         }
466
467         NETDATA.localStorage.current[key.toString()] = value;
468         return value;
469     };
470
471     NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
472         var keys = Object.keys(obj);
473         var len = keys.length;
474         while(len--) {
475             var i = keys[len];
476
477             if(typeof obj[i] === 'object') {
478                 //console.log('object ' + prefix + '.' + i.toString());
479                 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
480                 continue;
481             }
482
483             obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
484         }
485     };
486
487     NETDATA.setOption = function(key, value) {
488         if(key.toString() === 'setOptionCallback') {
489             if(typeof NETDATA.options.current.setOptionCallback === 'function') {
490                 NETDATA.options.current[key.toString()] = value;
491                 NETDATA.options.current.setOptionCallback();
492             }
493         }
494         else if(NETDATA.options.current[key.toString()] !== value) {
495             var name = 'options.' + key.toString();
496
497             if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
498                 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
499
500             //console.log(NETDATA.localStorage);
501             //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
502             //console.log(NETDATA.options);
503             NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
504
505             if(typeof NETDATA.options.current.setOptionCallback === 'function')
506                 NETDATA.options.current.setOptionCallback();
507         }
508
509         return true;
510     };
511
512     NETDATA.getOption = function(key) {
513         return NETDATA.options.current[key.toString()];
514     };
515
516     // read settings from local storage
517     NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
518
519     // always start with this option enabled.
520     NETDATA.setOption('stop_updates_when_focus_is_lost', true);
521
522     NETDATA.resetOptions = function() {
523         var keys = Object.keys(NETDATA.localStorage.default);
524         var len = keys.length;
525         while(len--) {
526             var i = keys[len];
527             var a = i.split('.');
528
529             if(a[0] === 'options') {
530                 if(a[1] === 'setOptionCallback') continue;
531                 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
532                 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
533
534                 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
535             }
536             else if(a[0] === 'chart_heights') {
537                 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
538                     NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
539                 }
540             }
541         }
542     };
543
544     // ----------------------------------------------------------------------------------------------------------------
545
546     if(NETDATA.options.debug.main_loop === true)
547         console.log('welcome to NETDATA');
548
549     NETDATA.onresizeCallback = null;
550     NETDATA.onresize = function() {
551         NETDATA.options.last_resized = Date.now();
552         NETDATA.onscroll();
553
554         if(typeof NETDATA.onresizeCallback === 'function')
555             NETDATA.onresizeCallback();
556     };
557
558     NETDATA.onscroll_updater_count = 0;
559     NETDATA.onscroll_updater_running = false;
560     NETDATA.onscroll_updater_last_run = 0;
561     NETDATA.onscroll_updater_watchdog = null;
562     NETDATA.onscroll_updater_max_duration = 0;
563     NETDATA.onscroll_updater_above_threshold_count = 0;
564     NETDATA.onscroll_updater = function() {
565         NETDATA.onscroll_updater_running = true;
566         NETDATA.onscroll_updater_count++;
567         var start = Date.now();
568
569         var targets = NETDATA.options.targets;
570         var len = targets.length;
571
572         // when the user scrolls he sees that we have
573         // hidden all the not-visible charts
574         // using this little function we try to switch
575         // the charts back to visible quickly
576
577
578         if(NETDATA.options.abort_ajax_on_scroll === true) {
579             // we have to cancel pending requests too
580
581             while (len--) {
582                 if (targets[len]._updating === true) {
583                     if (typeof targets[len].xhr !== 'undefined') {
584                         targets[len].xhr.abort();
585                         targets[len].running = false;
586                         targets[len]._updating = false;
587                     }
588                     targets[len].isVisible();
589                 }
590             }
591         }
592         else {
593             // just find which chart is visible
594
595             while (len--)
596                 targets[len].isVisible();
597         }
598
599         var end = Date.now();
600         // console.log('scroll No ' + NETDATA.onscroll_updater_count + ' calculation took ' + (end - start).toString() + ' ms');
601
602         if(NETDATA.options.current.async_on_scroll === false) {
603             var dt = end - start;
604             if(dt > NETDATA.onscroll_updater_max_duration) {
605                 // console.log('max onscroll event handler duration increased to ' + dt);
606                 NETDATA.onscroll_updater_max_duration = dt;
607             }
608
609             if(dt > NETDATA.options.current.onscroll_worker_duration_threshold) {
610                 // console.log('slow: ' + dt);
611                 NETDATA.onscroll_updater_above_threshold_count++;
612
613                 if(NETDATA.onscroll_updater_above_threshold_count > 2 && NETDATA.onscroll_updater_above_threshold_count * 100 / NETDATA.onscroll_updater_count > 2) {
614                     NETDATA.setOption('async_on_scroll', true);
615                     console.log('NETDATA: your browser is slow - enabling asynchronous onscroll event handler.');
616                 }
617             }
618         }
619
620         NETDATA.onscroll_updater_last_run = start;
621         NETDATA.onscroll_updater_running = false;
622     };
623
624     NETDATA.onscroll = function() {
625         // console.log('onscroll');
626
627         NETDATA.options.last_page_scroll = Date.now();
628         NETDATA.options.auto_refresher_stop_until = 0;
629
630         if(NETDATA.options.targets === null) return;
631
632         if(NETDATA.options.current.async_on_scroll === true) {
633             // async
634             if(NETDATA.onscroll_updater_running === false) {
635                 NETDATA.onscroll_updater_running = true;
636                 setTimeout(NETDATA.onscroll_updater, 0);
637             }
638             else {
639                 if(NETDATA.onscroll_updater_watchdog !== null)
640                     clearTimeout(NETDATA.onscroll_updater_watchdog);
641
642                 NETDATA.onscroll_updater_watchdog = setTimeout(function() {
643                     if(NETDATA.onscroll_updater_running === false && NETDATA.options.last_page_scroll > NETDATA.onscroll_updater_last_run) {
644                         // console.log('watchdog');
645                         NETDATA.onscroll_updater();
646                     }
647
648                     NETDATA.onscroll_updater_watchdog = null;
649                 }, 200);
650             }
651         }
652         else {
653             // sync
654             NETDATA.onscroll_updater();
655         }
656     };
657
658     window.onresize = NETDATA.onresize;
659     window.onscroll = NETDATA.onscroll;
660
661     // ----------------------------------------------------------------------------------------------------------------
662     // Error Handling
663
664     NETDATA.errorCodes = {
665         100: { message: "Cannot load chart library", alert: true },
666         101: { message: "Cannot load jQuery", alert: true },
667         402: { message: "Chart library not found", alert: false },
668         403: { message: "Chart library not enabled/is failed", alert: false },
669         404: { message: "Chart not found", alert: false },
670         405: { message: "Cannot download charts index from server", alert: true },
671         406: { message: "Invalid charts index downloaded from server", alert: true },
672         407: { message: "Cannot HELLO netdata server", alert: false },
673         408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
674         409: { message: "Cannot ACCESS netdata registry", alert: false },
675         410: { message: "Netdata registry ACCESS failed", alert: false },
676         411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
677         412: { message: "Netdata registry DELETE failed", alert: false },
678         413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
679         414: { message: "Netdata registry SWITCH failed", alert: false },
680         415: { message: "Netdata alarms download failed", alert: false },
681         416: { message: "Netdata alarms log download failed", alert: false },
682         417: { message: "Netdata registry server send invalid response to SEARCH ", alert: false },
683         418: { message: "Netdata registry SEARCH failed", alert: false }
684     };
685     NETDATA.errorLast = {
686         code: 0,
687         message: "",
688         datetime: 0
689     };
690
691     NETDATA.error = function(code, msg) {
692         NETDATA.errorLast.code = code;
693         NETDATA.errorLast.message = msg;
694         NETDATA.errorLast.datetime = Date.now();
695
696         console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
697
698         var ret = true;
699         if(typeof netdataErrorCallback === 'function') {
700            ret = netdataErrorCallback('system', code, msg);
701         }
702
703         if(ret && NETDATA.errorCodes[code].alert)
704             alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
705     };
706
707     NETDATA.errorReset = function() {
708         NETDATA.errorLast.code = 0;
709         NETDATA.errorLast.message = "You are doing fine!";
710         NETDATA.errorLast.datetime = 0;
711     };
712
713     // ----------------------------------------------------------------------------------------------------------------
714     // commonMin & commonMax
715
716     NETDATA.commonMin = {
717         keys: {},
718         latest: {},
719
720         get: function(state) {
721             if(typeof state.__commonMin === 'undefined') {
722                 // get the commonMin setting
723                 var self = $(state.element);
724                 state.__commonMin = self.data('common-min') || null;
725             }
726
727             var min = state.data.min;
728             var name = state.__commonMin;
729
730             if(name === null) {
731                 // we don't need commonMin
732                 //state.log('no need for commonMin');
733                 return min;
734             }
735
736             var t = this.keys[name];
737             if(typeof t === 'undefined') {
738                 // add our commonMin
739                 this.keys[name] = {};
740                 t = this.keys[name];
741             }
742
743             var uuid = state.uuid;
744             if(typeof t[uuid] !== 'undefined') {
745                 if(t[uuid] === min) {
746                     //state.log('commonMin ' + state.__commonMin + ' not changed: ' + this.latest[name]);
747                     return this.latest[name];
748                 }
749                 else if(min < this.latest[name]) {
750                     //state.log('commonMin ' + state.__commonMin + ' increased: ' + min);
751                     t[uuid] = min;
752                     this.latest[name] = min;
753                     return min;
754                 }
755             }
756
757             // add our min
758             t[uuid] = min;
759
760             // find the common min
761             var m = min;
762             for(var i in t)
763                 if(t.hasOwnProperty(i) && t[i] < m) m = t[i];
764
765             //state.log('commonMin ' + state.__commonMin + ' updated: ' + m);
766             this.latest[name] = m;
767             return m;
768         }
769     };
770
771     NETDATA.commonMax = {
772         keys: {},
773         latest: {},
774
775         get: function(state) {
776             if(typeof state.__commonMax === 'undefined') {
777                 // get the commonMax setting
778                 var self = $(state.element);
779                 state.__commonMax = self.data('common-max') || null;
780             }
781
782             var max = state.data.max;
783             var name = state.__commonMax;
784
785             if(name === null) {
786                 // we don't need commonMax
787                 //state.log('no need for commonMax');
788                 return max;
789             }
790
791             var t = this.keys[name];
792             if(typeof t === 'undefined') {
793                 // add our commonMax
794                 this.keys[name] = {};
795                 t = this.keys[name];
796             }
797
798             var uuid = state.uuid;
799             if(typeof t[uuid] !== 'undefined') {
800                 if(t[uuid] === max) {
801                     //state.log('commonMax ' + state.__commonMax + ' not changed: ' + this.latest[name]);
802                     return this.latest[name];
803                 }
804                 else if(max > this.latest[name]) {
805                     //state.log('commonMax ' + state.__commonMax + ' increased: ' + max);
806                     t[uuid] = max;
807                     this.latest[name] = max;
808                     return max;
809                 }
810             }
811
812             // add our max
813             t[uuid] = max;
814
815             // find the common max
816             var m = max;
817             for(var i in t)
818                 if(t.hasOwnProperty(i) && t[i] > m) m = t[i];
819
820             //state.log('commonMax ' + state.__commonMax + ' updated: ' + m);
821             this.latest[name] = m;
822             return m;
823         }
824     };
825
826     // ----------------------------------------------------------------------------------------------------------------
827     // Chart Registry
828
829     // When multiple charts need the same chart, we avoid downloading it
830     // multiple times (and having it in browser memory multiple time)
831     // by using this registry.
832
833     // Every time we download a chart definition, we save it here with .add()
834     // Then we try to get it back with .get(). If that fails, we download it.
835
836     NETDATA.fixHost = function(host) {
837         while(host.slice(-1) === '/')
838             host = host.substring(0, host.length - 1);
839
840         return host;
841     };
842
843     NETDATA.chartRegistry = {
844         charts: {},
845
846         fixid: function(id) {
847             return id.replace(/:/g, "_").replace(/\//g, "_");
848         },
849
850         add: function(host, id, data) {
851             host = this.fixid(host);
852             id   = this.fixid(id);
853
854             if(typeof this.charts[host] === 'undefined')
855                 this.charts[host] = {};
856
857             //console.log('added ' + host + '/' + id);
858             this.charts[host][id] = data;
859         },
860
861         get: function(host, id) {
862             host = this.fixid(host);
863             id   = this.fixid(id);
864
865             if(typeof this.charts[host] === 'undefined')
866                 return null;
867
868             if(typeof this.charts[host][id] === 'undefined')
869                 return null;
870
871             //console.log('cached ' + host + '/' + id);
872             return this.charts[host][id];
873         },
874
875         downloadAll: function(host, callback) {
876             host = NETDATA.fixHost(host);
877
878             var self = this;
879
880             $.ajax({
881                 url: host + '/api/v1/charts',
882                 async: true,
883                 cache: false,
884                 xhrFields: { withCredentials: true } // required for the cookie
885             })
886             .done(function(data) {
887                 if(data !== null) {
888                     var h = NETDATA.chartRegistry.fixid(host);
889                     self.charts[h] = data.charts;
890                 }
891                 else NETDATA.error(406, host + '/api/v1/charts');
892
893                 if(typeof callback === 'function')
894                     return callback(data);
895             })
896             .fail(function() {
897                 NETDATA.error(405, host + '/api/v1/charts');
898
899                 if(typeof callback === 'function')
900                     return callback(null);
901             });
902         }
903     };
904
905     // ----------------------------------------------------------------------------------------------------------------
906     // Global Pan and Zoom on charts
907
908     // Using this structure are synchronize all the charts, so that
909     // when you pan or zoom one, all others are automatically refreshed
910     // to the same timespan.
911
912     NETDATA.globalPanAndZoom = {
913         seq: 0,                 // timestamp ms
914                                 // every time a chart is panned or zoomed
915                                 // we set the timestamp here
916                                 // then we use it as a sequence number
917                                 // to find if other charts are syncronized
918                                 // to this timerange
919
920         master: null,           // the master chart (state), to which all others
921                                 // are synchronized
922
923         force_before_ms: null,  // the timespan to sync all other charts
924         force_after_ms: null,
925
926         callback: null,
927
928         // set a new master
929         setMaster: function(state, after, before) {
930             if(NETDATA.options.current.sync_pan_and_zoom === false)
931                 return;
932
933             if(this.master !== null && this.master !== state)
934                 this.master.resetChart(true, true);
935
936             var now = Date.now();
937             this.master = state;
938             this.seq = now;
939             this.force_after_ms = after;
940             this.force_before_ms = before;
941             NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
942
943             if(typeof this.callback === 'function')
944                 this.callback(true, after, before);
945         },
946
947         // clear the master
948         clearMaster: function() {
949             if(this.master !== null) {
950                 var st = this.master;
951                 this.master = null;
952                 st.resetChart();
953             }
954
955             this.master = null;
956             this.seq = 0;
957             this.force_after_ms = null;
958             this.force_before_ms = null;
959             NETDATA.options.auto_refresher_stop_until = 0;
960
961             if(typeof this.callback === 'function')
962                 this.callback(false, 0, 0);
963         },
964
965         // is the given state the master of the global
966         // pan and zoom sync?
967         isMaster: function(state) {
968             return (this.master === state);
969         },
970
971         // are we currently have a global pan and zoom sync?
972         isActive: function() {
973             return (this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0);
974         },
975
976         // check if a chart, other than the master
977         // needs to be refreshed, due to the global pan and zoom
978         shouldBeAutoRefreshed: function(state) {
979             if(this.master === null || this.seq === 0)
980                 return false;
981
982             //if(state.needsRecreation())
983             //  return true;
984
985             return (!(state.tm.pan_and_zoom_seq === this.seq));
986         }
987     };
988
989     // ----------------------------------------------------------------------------------------------------------------
990     // dimensions selection
991
992     // FIXME
993     // move color assignment to dimensions, here
994
995     var dimensionStatus = function(parent, label, name_div, value_div, color) {
996         this.enabled = false;
997         this.parent = parent;
998         this.label = label;
999         this.name_div = null;
1000         this.value_div = null;
1001         this.color = NETDATA.themes.current.foreground;
1002         this.selected = (parent.unselected_count === 0);
1003
1004         this.setOptions(name_div, value_div, color);
1005     };
1006
1007     dimensionStatus.prototype.invalidate = function() {
1008         this.name_div = null;
1009         this.value_div = null;
1010         this.enabled = false;
1011     };
1012
1013     dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
1014         this.color = color;
1015
1016         if(this.name_div !== name_div) {
1017             this.name_div = name_div;
1018             this.name_div.title = this.label;
1019             this.name_div.style.color = this.color;
1020             if(this.selected === false)
1021                 this.name_div.className = 'netdata-legend-name not-selected';
1022             else
1023                 this.name_div.className = 'netdata-legend-name selected';
1024         }
1025
1026         if(this.value_div !== value_div) {
1027             this.value_div = value_div;
1028             this.value_div.title = this.label;
1029             this.value_div.style.color = this.color;
1030             if(this.selected === false)
1031                 this.value_div.className = 'netdata-legend-value not-selected';
1032             else
1033                 this.value_div.className = 'netdata-legend-value selected';
1034         }
1035
1036         this.enabled = true;
1037         this.setHandler();
1038     };
1039
1040     dimensionStatus.prototype.setHandler = function() {
1041         if(this.enabled === false) return;
1042
1043         var ds = this;
1044
1045         // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
1046         this.name_div.onclick = this.value_div.onclick = function(e) {
1047             e.preventDefault();
1048             if(ds.isSelected()) {
1049                 // this is selected
1050                 if(e.shiftKey === true || e.ctrlKey === true) {
1051                     // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
1052                     ds.unselect();
1053
1054                     if(ds.parent.countSelected() === 0)
1055                         ds.parent.selectAll();
1056                 }
1057                 else {
1058                     // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
1059                     if(ds.parent.countSelected() === 1) {
1060                         ds.parent.selectAll();
1061                     }
1062                     else {
1063                         ds.parent.selectNone();
1064                         ds.select();
1065                     }
1066                 }
1067             }
1068             else {
1069                 // this is not selected
1070                 if(e.shiftKey === true || e.ctrlKey === true) {
1071                     // control or shift key is pressed -> select this too
1072                     ds.select();
1073                 }
1074                 else {
1075                     // no key is pressed -> select only this
1076                     ds.parent.selectNone();
1077                     ds.select();
1078                 }
1079             }
1080
1081             ds.parent.state.redrawChart();
1082         }
1083     };
1084
1085     dimensionStatus.prototype.select = function() {
1086         if(this.enabled === false) return;
1087
1088         this.name_div.className = 'netdata-legend-name selected';
1089         this.value_div.className = 'netdata-legend-value selected';
1090         this.selected = true;
1091     };
1092
1093     dimensionStatus.prototype.unselect = function() {
1094         if(this.enabled === false) return;
1095
1096         this.name_div.className = 'netdata-legend-name not-selected';
1097         this.value_div.className = 'netdata-legend-value hidden';
1098         this.selected = false;
1099     };
1100
1101     dimensionStatus.prototype.isSelected = function() {
1102         return(this.enabled === true && this.selected === true);
1103     };
1104
1105     // ----------------------------------------------------------------------------------------------------------------
1106
1107     var dimensionsVisibility = function(state) {
1108         this.state = state;
1109         this.len = 0;
1110         this.dimensions = {};
1111         this.selected_count = 0;
1112         this.unselected_count = 0;
1113     };
1114
1115     dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
1116         if(typeof this.dimensions[label] === 'undefined') {
1117             this.len++;
1118             this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
1119         }
1120         else
1121             this.dimensions[label].setOptions(name_div, value_div, color);
1122
1123         return this.dimensions[label];
1124     };
1125
1126     dimensionsVisibility.prototype.dimensionGet = function(label) {
1127         return this.dimensions[label];
1128     };
1129
1130     dimensionsVisibility.prototype.invalidateAll = function() {
1131         var keys = Object.keys(this.dimensions);
1132         var len = keys.length;
1133         while(len--)
1134             this.dimensions[keys[len]].invalidate();
1135     };
1136
1137     dimensionsVisibility.prototype.selectAll = function() {
1138         var keys = Object.keys(this.dimensions);
1139         var len = keys.length;
1140         while(len--)
1141             this.dimensions[keys[len]].select();
1142     };
1143
1144     dimensionsVisibility.prototype.countSelected = function() {
1145         var selected = 0;
1146         var keys = Object.keys(this.dimensions);
1147         var len = keys.length;
1148         while(len--)
1149             if(this.dimensions[keys[len]].isSelected()) selected++;
1150
1151         return selected;
1152     };
1153
1154     dimensionsVisibility.prototype.selectNone = function() {
1155         var keys = Object.keys(this.dimensions);
1156         var len = keys.length;
1157         while(len--)
1158             this.dimensions[keys[len]].unselect();
1159     };
1160
1161     dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
1162         var ret = [];
1163         this.selected_count = 0;
1164         this.unselected_count = 0;
1165
1166         var len = array.length;
1167         while(len--) {
1168             var ds = this.dimensions[array[len]];
1169             if(typeof ds === 'undefined') {
1170                 // console.log(array[i] + ' is not found');
1171                 ret.unshift(false);
1172             }
1173             else if(ds.isSelected()) {
1174                 ret.unshift(true);
1175                 this.selected_count++;
1176             }
1177             else {
1178                 ret.unshift(false);
1179                 this.unselected_count++;
1180             }
1181         }
1182
1183         if(this.selected_count === 0 && this.unselected_count !== 0) {
1184             this.selectAll();
1185             return this.selected2BooleanArray(array);
1186         }
1187
1188         return ret;
1189     };
1190
1191
1192     // ----------------------------------------------------------------------------------------------------------------
1193     // global selection sync
1194
1195     NETDATA.globalSelectionSync = {
1196         state: null,
1197         dont_sync_before: 0,
1198         last_t: 0,
1199         slaves: [],
1200
1201         stop: function() {
1202             if(this.state !== null)
1203                 this.state.globalSelectionSyncStop();
1204         },
1205
1206         delay: function() {
1207             if(this.state !== null) {
1208                 this.state.globalSelectionSyncDelay();
1209             }
1210         }
1211     };
1212
1213     // ----------------------------------------------------------------------------------------------------------------
1214     // Our state object, where all per-chart values are stored
1215
1216     var chartState = function(element) {
1217         var self = $(element);
1218         this.element = element;
1219
1220         // IMPORTANT:
1221         // all private functions should use 'that', instead of 'this'
1222         var that = this;
1223
1224         /* error() - private
1225          * show an error instead of the chart
1226          */
1227         var error = function(msg) {
1228             var ret = true;
1229
1230             if(typeof netdataErrorCallback === 'function') {
1231                 ret = netdataErrorCallback('chart', that.id, msg);
1232             }
1233
1234             if(ret) {
1235                 that.element.innerHTML = that.id + ': ' + msg;
1236                 that.enabled = false;
1237                 that.current = that.pan;
1238             }
1239         };
1240
1241         // GUID - a unique identifier for the chart
1242         this.uuid = NETDATA.guid();
1243
1244         // string - the name of chart
1245         this.id = self.data('netdata');
1246
1247         // string - the key for localStorage settings
1248         this.settings_id = self.data('id') || null;
1249
1250         // the user given dimensions of the element
1251         this.width = self.data('width') || NETDATA.chartDefaults.width;
1252         this.height = self.data('height') || NETDATA.chartDefaults.height;
1253         this.height_original = this.height;
1254
1255         if(this.settings_id !== null) {
1256             this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1257                 // this is the callback that will be called
1258                 // if and when the user resets all localStorage variables
1259                 // to their defaults
1260
1261                 resizeChartToHeight(height);
1262             });
1263         }
1264
1265         // string - the netdata server URL, without any path
1266         this.host = self.data('host') || NETDATA.chartDefaults.host;
1267
1268         // make sure the host does not end with /
1269         // all netdata API requests use absolute paths
1270         while(this.host.slice(-1) === '/')
1271             this.host = this.host.substring(0, this.host.length - 1);
1272
1273         // string - the grouping method requested by the user
1274         this.method = self.data('method') || NETDATA.chartDefaults.method;
1275
1276         // the time-range requested by the user
1277         this.after = self.data('after') || NETDATA.chartDefaults.after;
1278         this.before = self.data('before') || NETDATA.chartDefaults.before;
1279
1280         // the pixels per point requested by the user
1281         this.pixels_per_point = self.data('pixels-per-point') || 1;
1282         this.points = self.data('points') || null;
1283
1284         // the dimensions requested by the user
1285         this.dimensions = self.data('dimensions') || null;
1286
1287         // the chart library requested by the user
1288         this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1289
1290         // how many retries we have made to load chart data from the server
1291         this.retries_on_data_failures = 0;
1292
1293         // object - the chart library used
1294         this.library = null;
1295
1296         // color management
1297         this.colors = null;
1298         this.colors_assigned = {};
1299         this.colors_available = null;
1300
1301         // the element already created by the user
1302         this.element_message = null;
1303
1304         // the element with the chart
1305         this.element_chart = null;
1306
1307         // the element with the legend of the chart (if created by us)
1308         this.element_legend = null;
1309         this.element_legend_childs = {
1310             hidden: null,
1311             title_date: null,
1312             title_time: null,
1313             title_units: null,
1314             perfect_scroller: null, // the container to apply perfect scroller to
1315             series: null
1316         };
1317
1318         this.chart_url = null;                      // string - the url to download chart info
1319         this.chart = null;                          // object - the chart as downloaded from the server
1320
1321         this.title = self.data('title') || null;    // the title of the chart
1322         this.units = self.data('units') || null;    // the units of the chart dimensions
1323         this.append_options = self.data('append-options') || null;  // additional options to pass to netdata
1324         this.override_options = self.data('override-options') || null;  // override options to pass to netdata
1325
1326         this.running = false;                       // boolean - true when the chart is being refreshed now
1327         this.validated = false;                     // boolean - has the chart been validated?
1328         this.enabled = true;                        // boolean - is the chart enabled for refresh?
1329         this.paused = false;                        // boolean - is the chart paused for any reason?
1330         this.selected = false;                      // boolean - is the chart shown a selection?
1331         this.debug = false;                         // boolean - console.log() debug info about this chart
1332
1333         this.netdata_first = 0;                     // milliseconds - the first timestamp in netdata
1334         this.netdata_last = 0;                      // milliseconds - the last timestamp in netdata
1335         this.requested_after = null;                // milliseconds - the timestamp of the request after param
1336         this.requested_before = null;               // milliseconds - the timestamp of the request before param
1337         this.requested_padding = null;
1338         this.view_after = 0;
1339         this.view_before = 0;
1340
1341         this.value_decimal_detail = -1;
1342         var d = self.data('decimal-digits');
1343         if(typeof d === 'number') {
1344             this.value_decimal_detail = 1;
1345             while(d-- > 0)
1346                 this.value_decimal_detail *= 10;
1347         }
1348
1349         this.auto = {
1350             name: 'auto',
1351             autorefresh: true,
1352             force_update_at: 0, // the timestamp to force the update at
1353             force_before_ms: null,
1354             force_after_ms: null
1355         };
1356         this.pan = {
1357             name: 'pan',
1358             autorefresh: false,
1359             force_update_at: 0, // the timestamp to force the update at
1360             force_before_ms: null,
1361             force_after_ms: null
1362         };
1363         this.zoom = {
1364             name: 'zoom',
1365             autorefresh: false,
1366             force_update_at: 0, // the timestamp to force the update at
1367             force_before_ms: null,
1368             force_after_ms: null
1369         };
1370
1371         // this is a pointer to one of the sub-classes below
1372         // auto, pan, zoom
1373         this.current = this.auto;
1374
1375         // check the requested library is available
1376         // we don't initialize it here - it will be initialized when
1377         // this chart will be first used
1378         if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1379             NETDATA.error(402, that.library_name);
1380             error('chart library "' + that.library_name + '" is not found');
1381             return;
1382         }
1383         else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1384             NETDATA.error(403, that.library_name);
1385             error('chart library "' + that.library_name + '" is not enabled');
1386             return;
1387         }
1388         else
1389             that.library = NETDATA.chartLibraries[that.library_name];
1390
1391         // milliseconds - the time the last refresh took
1392         this.refresh_dt_ms = 0;
1393
1394         // if we need to report the rendering speed
1395         // find the element that needs to be updated
1396         var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1397
1398         if(refresh_dt_element_name !== null)
1399             this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1400         else
1401             this.refresh_dt_element = null;
1402
1403         this.dimensions_visibility = new dimensionsVisibility(this);
1404
1405         this._updating = false;
1406
1407         // ============================================================================================================
1408         // PRIVATE FUNCTIONS
1409
1410         var createDOM = function() {
1411             if(that.enabled === false) return;
1412
1413             if(that.element_message !== null) that.element_message.innerHTML = '';
1414             if(that.element_legend !== null) that.element_legend.innerHTML = '';
1415             if(that.element_chart !== null) that.element_chart.innerHTML = '';
1416
1417             that.element.innerHTML = '';
1418
1419             that.element_message = document.createElement('div');
1420             that.element_message.className = 'netdata-message icon hidden';
1421             that.element.appendChild(that.element_message);
1422
1423             that.element_chart = document.createElement('div');
1424             that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1425             that.element.appendChild(that.element_chart);
1426
1427             if(that.hasLegend() === true) {
1428                 that.element.className = "netdata-container-with-legend";
1429                 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1430
1431                 that.element_legend = document.createElement('div');
1432                 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1433                 that.element.appendChild(that.element_legend);
1434             }
1435             else {
1436                 that.element.className = "netdata-container";
1437                 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1438
1439                 that.element_legend = null;
1440             }
1441             that.element_legend_childs.series = null;
1442
1443             if(typeof(that.width) === 'string')
1444                 $(that.element).css('width', that.width);
1445             else if(typeof(that.width) === 'number')
1446                 $(that.element).css('width', that.width + 'px');
1447
1448             if(typeof(that.library.aspect_ratio) === 'undefined') {
1449                 if(typeof(that.height) === 'string')
1450                     that.element.style.height = that.height;
1451                 else if(typeof(that.height) === 'number')
1452                     that.element.style.height = that.height.toString() + 'px';
1453             }
1454             else {
1455                 var w = that.element.offsetWidth;
1456                 if(w === null || w === 0) {
1457                     // the div is hidden
1458                     // this will resize the chart when next viewed
1459                     that.tm.last_resized = 0;
1460                 }
1461                 else
1462                     that.element.style.height = (w * that.library.aspect_ratio / 100).toString() + 'px';
1463             }
1464
1465             if(NETDATA.chartDefaults.min_width !== null)
1466                 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1467
1468             that.tm.last_dom_created = Date.now();
1469
1470             showLoading();
1471         };
1472
1473         /* init() private
1474          * initialize state variables
1475          * destroy all (possibly) created state elements
1476          * create the basic DOM for a chart
1477          */
1478         var init = function() {
1479             if(that.enabled === false) return;
1480
1481             that.paused = false;
1482             that.selected = false;
1483
1484             that.chart_created = false;         // boolean - is the library.create() been called?
1485             that.updates_counter = 0;           // numeric - the number of refreshes made so far
1486             that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1487             that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1488
1489             that.tm = {
1490                 last_initialized: 0,        // milliseconds - the timestamp it was last initialized
1491                 last_dom_created: 0,        // milliseconds - the timestamp its DOM was last created
1492                 last_mode_switch: 0,        // milliseconds - the timestamp it switched modes
1493
1494                 last_info_downloaded: 0,    // milliseconds - the timestamp we downloaded the chart
1495                 last_updated: 0,            // the timestamp the chart last updated with data
1496                 pan_and_zoom_seq: 0,        // the sequence number of the global synchronization
1497                                             // between chart.
1498                                             // Used with NETDATA.globalPanAndZoom.seq
1499                 last_visible_check: 0,      // the time we last checked if it is visible
1500                 last_resized: 0,            // the time the chart was resized
1501                 last_hidden: 0,             // the time the chart was hidden
1502                 last_unhidden: 0,           // the time the chart was unhidden
1503                 last_autorefreshed: 0       // the time the chart was last refreshed
1504             };
1505
1506             that.data = null;               // the last data as downloaded from the netdata server
1507             that.data_url = 'invalid://';   // string - the last url used to update the chart
1508             that.data_points = 0;           // number - the number of points returned from netdata
1509             that.data_after = 0;            // milliseconds - the first timestamp of the data
1510             that.data_before = 0;           // milliseconds - the last timestamp of the data
1511             that.data_update_every = 0;     // milliseconds - the frequency to update the data
1512
1513             that.tm.last_initialized = Date.now();
1514             createDOM();
1515
1516             that.setMode('auto');
1517         };
1518
1519         var maxMessageFontSize = function() {
1520             var screenHeight = screen.height;
1521             var el = that.element;
1522
1523             // normally we want a font size, as tall as the element
1524             var h = el.clientHeight;
1525
1526             // but give it some air, 20% let's say, or 5 pixels min
1527             var lost = Math.max(h * 0.2, 5);
1528             h -= lost;
1529
1530             // center the text, vertically
1531             var paddingTop = (lost - 5) / 2;
1532
1533             // but check the width too
1534             // it should fit 10 characters in it
1535             var w = el.clientWidth / 10;
1536             if(h > w) {
1537                 paddingTop += (h - w) / 2;
1538                 h = w;
1539             }
1540
1541             // and don't make it too huge
1542             // 5% of the screen size is good
1543             if(h > screenHeight / 20) {
1544                 paddingTop += (h - (screenHeight / 20)) / 2;
1545                 h = screenHeight / 20;
1546             }
1547
1548             // set it
1549             that.element_message.style.fontSize = h.toString() + 'px';
1550             that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1551         };
1552
1553         var showMessageIcon = function(icon) {
1554             that.element_message.innerHTML = icon;
1555             maxMessageFontSize();
1556             $(that.element_message).removeClass('hidden');
1557             that.___messageHidden___ = undefined;
1558         };
1559
1560         var hideMessage = function() {
1561             if(typeof that.___messageHidden___ === 'undefined') {
1562                 that.___messageHidden___ = true;
1563                 $(that.element_message).addClass('hidden');
1564             }
1565         };
1566
1567         var showRendering = function() {
1568             var icon;
1569             if(that.chart !== null) {
1570                 if(that.chart.chart_type === 'line')
1571                     icon = '<i class="fa fa-line-chart"></i>';
1572                 else
1573                     icon = '<i class="fa fa-area-chart"></i>';
1574             }
1575             else
1576                 icon = '<i class="fa fa-area-chart"></i>';
1577
1578             showMessageIcon(icon + ' netdata');
1579         };
1580
1581         var showLoading = function() {
1582             if(that.chart_created === false) {
1583                 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1584                 return true;
1585             }
1586             return false;
1587         };
1588
1589         var isHidden = function() {
1590             return (typeof that.___chartIsHidden___ !== 'undefined');
1591         };
1592
1593         // hide the chart, when it is not visible - called from isVisible()
1594         var hideChart = function() {
1595             // hide it, if it is not already hidden
1596             if(isHidden() === true) return;
1597
1598             if(that.chart_created === true) {
1599                 if(NETDATA.options.current.destroy_on_hide === true) {
1600                     // we should destroy it
1601                     init();
1602                 }
1603                 else {
1604                     showRendering();
1605                     that.element_chart.style.display = 'none';
1606                     if(that.element_legend !== null) that.element_legend.style.display = 'none';
1607                     that.tm.last_hidden = Date.now();
1608
1609                     // de-allocate data
1610                     // This works, but I not sure there are no corner cases somewhere
1611                     // so it is commented - if the user has memory issues he can
1612                     // set Destroy on Hide for all charts
1613                     // that.data = null;
1614                 }
1615             }
1616
1617             that.___chartIsHidden___ = true;
1618         };
1619
1620         // unhide the chart, when it is visible - called from isVisible()
1621         var unhideChart = function() {
1622             if(isHidden() === false) return;
1623
1624             that.___chartIsHidden___ = undefined;
1625             that.updates_since_last_unhide = 0;
1626
1627             if(that.chart_created === false) {
1628                 // we need to re-initialize it, to show our background
1629                 // logo in bootstrap tabs, until the chart loads
1630                 init();
1631             }
1632             else {
1633                 that.tm.last_unhidden = Date.now();
1634                 that.element_chart.style.display = '';
1635                 if(that.element_legend !== null) that.element_legend.style.display = '';
1636                 resizeChart();
1637                 hideMessage();
1638             }
1639         };
1640
1641         var canBeRendered = function() {
1642             return (isHidden() === false && that.isVisible(true) === true);
1643         };
1644
1645         // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1646         var callChartLibraryUpdateSafely = function(data) {
1647             var status;
1648
1649             if(canBeRendered() === false)
1650                 return false;
1651
1652             if(NETDATA.options.debug.chart_errors === true)
1653                 status = that.library.update(that, data);
1654             else {
1655                 try {
1656                     status = that.library.update(that, data);
1657                 }
1658                 catch(err) {
1659                     status = false;
1660                 }
1661             }
1662
1663             if(status === false) {
1664                 error('chart failed to be updated as ' + that.library_name);
1665                 return false;
1666             }
1667
1668             return true;
1669         };
1670
1671         // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1672         var callChartLibraryCreateSafely = function(data) {
1673             var status;
1674
1675             if(canBeRendered() === false)
1676                 return false;
1677
1678             if(NETDATA.options.debug.chart_errors === true)
1679                 status = that.library.create(that, data);
1680             else {
1681                 try {
1682                     status = that.library.create(that, data);
1683                 }
1684                 catch(err) {
1685                     status = false;
1686                 }
1687             }
1688
1689             if(status === false) {
1690                 error('chart failed to be created as ' + that.library_name);
1691                 return false;
1692             }
1693
1694             that.chart_created = true;
1695             that.updates_since_last_creation = 0;
1696             return true;
1697         };
1698
1699         // ----------------------------------------------------------------------------------------------------------------
1700         // Chart Resize
1701
1702         // resizeChart() - private
1703         // to be called just before the chart library to make sure that
1704         // a properly sized dom is available
1705         var resizeChart = function() {
1706             if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1707                 if(that.chart_created === false) return;
1708
1709                 if(that.needsRecreation()) {
1710                     init();
1711                 }
1712                 else if(typeof that.library.resize === 'function') {
1713                     that.library.resize(that);
1714
1715                     if(that.element_legend_childs.perfect_scroller !== null)
1716                         Ps.update(that.element_legend_childs.perfect_scroller);
1717
1718                     maxMessageFontSize();
1719                 }
1720
1721                 that.tm.last_resized = Date.now();
1722             }
1723         };
1724
1725         // this is the actual chart resize algorithm
1726         // it will:
1727         // - resize the entire container
1728         // - update the internal states
1729         // - resize the chart as the div changes height
1730         // - update the scrollbar of the legend
1731         var resizeChartToHeight = function(h) {
1732             // console.log(h);
1733             that.element.style.height = h;
1734
1735             if(that.settings_id !== null)
1736                 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1737
1738             var now = Date.now();
1739             NETDATA.options.last_page_scroll = now;
1740             NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1741
1742             // force a resize
1743             that.tm.last_resized = 0;
1744             resizeChart();
1745         };
1746
1747         this.resizeHandler = function(e) {
1748             e.preventDefault();
1749
1750             if(typeof this.event_resize === 'undefined'
1751                 || this.event_resize.chart_original_w === 'undefined'
1752                 || this.event_resize.chart_original_h === 'undefined')
1753                 this.event_resize = {
1754                     chart_original_w: this.element.clientWidth,
1755                     chart_original_h: this.element.clientHeight,
1756                     last: 0
1757                 };
1758
1759             if(e.type === 'touchstart') {
1760                 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1761                 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1762             }
1763             else {
1764                 this.event_resize.mouse_start_x = e.clientX;
1765                 this.event_resize.mouse_start_y = e.clientY;
1766             }
1767
1768             this.event_resize.chart_start_w = this.element.clientWidth;
1769             this.event_resize.chart_start_h = this.element.clientHeight;
1770             this.event_resize.chart_last_w = this.element.clientWidth;
1771             this.event_resize.chart_last_h = this.element.clientHeight;
1772
1773             var now = Date.now();
1774             if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed && this.element_legend_childs.perfect_scroller !== null) {
1775                 // double click / double tap event
1776
1777                 // console.dir(this.element_legend_childs.content);
1778                 // console.dir(this.element_legend_childs.perfect_scroller);
1779
1780                 // the optimal height of the chart
1781                 // showing the entire legend
1782                 var optimal = this.event_resize.chart_last_h
1783                         + this.element_legend_childs.perfect_scroller.scrollHeight
1784                         - this.element_legend_childs.perfect_scroller.clientHeight;
1785
1786                 // if we are not optimal, be optimal
1787                 if(this.event_resize.chart_last_h !== optimal) {
1788                     // this.log('resize to optimal, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1789                     resizeChartToHeight(optimal.toString() + 'px');
1790                 }
1791
1792                 // else if the current height is not the original/saved height
1793                 // reset to the original/saved height
1794                 else if(this.event_resize.chart_last_h !== this.event_resize.chart_original_h) {
1795                     // this.log('resize to original, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1796                     resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1797                 }
1798
1799                 // else if the current height is not the internal default height
1800                 // reset to the internal default height
1801                 else if((this.event_resize.chart_last_h.toString() + 'px') !== this.height_original) {
1802                     // this.log('resize to internal default, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString());
1803                     resizeChartToHeight(this.height_original.toString());
1804                 }
1805
1806                 // else if the current height is not the firstchild's clientheight
1807                 // resize to it
1808                 else if(typeof this.element_legend_childs.perfect_scroller.firstChild !== 'undefined') {
1809                     var parent_rect = this.element.getBoundingClientRect();
1810                     var content_rect = this.element_legend_childs.perfect_scroller.firstElementChild.getBoundingClientRect();
1811                     var wanted = content_rect.top - parent_rect.top + this.element_legend_childs.perfect_scroller.firstChild.clientHeight + 18; // 15 = toolbox + 3 space
1812
1813                     // console.log(parent_rect);
1814                     // console.log(content_rect);
1815                     // console.log(wanted);
1816
1817                     // this.log('resize to firstChild, current = ' + this.event_resize.chart_last_h.toString() + 'px, original = ' + this.event_resize.chart_original_h.toString() + 'px, optimal = ' + optimal.toString() + 'px, internal = ' + this.height_original.toString() + 'px, firstChild = ' + wanted.toString() + 'px' );
1818                     if(this.event_resize.chart_last_h !== wanted)
1819                         resizeChartToHeight(wanted.toString() + 'px');
1820                 }
1821             }
1822             else {
1823                 this.event_resize.last = now;
1824
1825                 // process movement event
1826                 document.onmousemove =
1827                 document.ontouchmove =
1828                 this.element_legend_childs.resize_handler.onmousemove =
1829                 this.element_legend_childs.resize_handler.ontouchmove =
1830                     function(e) {
1831                         var y = null;
1832
1833                         switch(e.type) {
1834                             case 'mousemove': y = e.clientY; break;
1835                             case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1836                         }
1837
1838                         if(y !== null) {
1839                             var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1840
1841                             if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1842                                 resizeChartToHeight(newH.toString() + 'px');
1843                                 that.event_resize.chart_last_h = newH;
1844                             }
1845                         }
1846                     };
1847
1848                 // process end event
1849                 document.onmouseup =
1850                 document.ontouchend =
1851                 this.element_legend_childs.resize_handler.onmouseup =
1852                 this.element_legend_childs.resize_handler.ontouchend =
1853                     function() {
1854                         // remove all the hooks
1855                         document.onmouseup =
1856                         document.onmousemove =
1857                         document.ontouchmove =
1858                         document.ontouchend =
1859                         that.element_legend_childs.resize_handler.onmousemove =
1860                         that.element_legend_childs.resize_handler.ontouchmove =
1861                         that.element_legend_childs.resize_handler.onmouseout =
1862                         that.element_legend_childs.resize_handler.onmouseup =
1863                         that.element_legend_childs.resize_handler.ontouchend =
1864                             null;
1865
1866                         // allow auto-refreshes
1867                         NETDATA.options.auto_refresher_stop_until = 0;
1868                     };
1869             }
1870         };
1871
1872
1873         var noDataToShow = function() {
1874             showMessageIcon('<i class="fa fa-warning"></i> empty');
1875             that.legendUpdateDOM();
1876             that.tm.last_autorefreshed = Date.now();
1877             // that.data_update_every = 30 * 1000;
1878             //that.element_chart.style.display = 'none';
1879             //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1880             //that.___chartIsHidden___ = true;
1881         };
1882
1883         // ============================================================================================================
1884         // PUBLIC FUNCTIONS
1885
1886         this.error = function(msg) {
1887             error(msg);
1888         };
1889
1890         this.setMode = function(m) {
1891             if(this.current !== null && this.current.name === m) return;
1892
1893             if(m === 'auto')
1894                 this.current = this.auto;
1895             else if(m === 'pan')
1896                 this.current = this.pan;
1897             else if(m === 'zoom')
1898                 this.current = this.zoom;
1899             else
1900                 this.current = this.auto;
1901
1902             this.current.force_update_at = 0;
1903             this.current.force_before_ms = null;
1904             this.current.force_after_ms = null;
1905
1906             this.tm.last_mode_switch = Date.now();
1907         };
1908
1909         // ----------------------------------------------------------------------------------------------------------------
1910         // global selection sync
1911
1912         // prevent to global selection sync for some time
1913         this.globalSelectionSyncDelay = function(ms) {
1914             if(NETDATA.options.current.sync_selection === false)
1915                 return;
1916
1917             if(typeof ms === 'number')
1918                 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + ms;
1919             else
1920                 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + NETDATA.options.current.sync_selection_delay;
1921         };
1922
1923         // can we globally apply selection sync?
1924         this.globalSelectionSyncAbility = function() {
1925             if(NETDATA.options.current.sync_selection === false)
1926                 return false;
1927
1928             return (!(NETDATA.globalSelectionSync.dont_sync_before > Date.now()));
1929         };
1930
1931         this.globalSelectionSyncIsMaster = function() {
1932             return (NETDATA.globalSelectionSync.state === this);
1933         };
1934
1935         // this chart is the master of the global selection sync
1936         this.globalSelectionSyncBeMaster = function() {
1937             // am I the master?
1938             if(this.globalSelectionSyncIsMaster()) {
1939                 if(this.debug === true)
1940                     this.log('sync: I am the master already.');
1941
1942                 return;
1943             }
1944
1945             if(NETDATA.globalSelectionSync.state) {
1946                 if(this.debug === true)
1947                     this.log('sync: I am not the sync master. Resetting global sync.');
1948
1949                 this.globalSelectionSyncStop();
1950             }
1951
1952             // become the master
1953             if(this.debug === true)
1954                 this.log('sync: becoming sync master.');
1955
1956             this.selected = true;
1957             NETDATA.globalSelectionSync.state = this;
1958
1959             // find the all slaves
1960             var targets = NETDATA.options.targets;
1961             var len = targets.length;
1962             while(len--) {
1963                 var st = targets[len];
1964
1965                 if(st === this) {
1966                     if(this.debug === true)
1967                         st.log('sync: not adding me to sync');
1968                 }
1969                 else if(st.globalSelectionSyncIsEligible()) {
1970                     if(this.debug === true)
1971                         st.log('sync: adding to sync as slave');
1972
1973                     st.globalSelectionSyncBeSlave();
1974                 }
1975             }
1976
1977             // this.globalSelectionSyncDelay(100);
1978         };
1979
1980         // can the chart participate to the global selection sync as a slave?
1981         this.globalSelectionSyncIsEligible = function() {
1982             return (this.enabled === true
1983                 && this.library !== null
1984                 && typeof this.library.setSelection === 'function'
1985                 && this.isVisible() === true
1986                 && this.chart_created === true);
1987         };
1988
1989         // this chart becomes a slave of the global selection sync
1990         this.globalSelectionSyncBeSlave = function() {
1991             if(NETDATA.globalSelectionSync.state !== this)
1992                 NETDATA.globalSelectionSync.slaves.push(this);
1993         };
1994
1995         // sync all the visible charts to the given time
1996         // this is to be called from the chart libraries
1997         this.globalSelectionSync = function(t) {
1998             if(this.globalSelectionSyncAbility() === false)
1999                 return;
2000
2001             if(this.globalSelectionSyncIsMaster() === false) {
2002                 if(this.debug === true)
2003                     this.log('sync: trying to be sync master.');
2004
2005                 this.globalSelectionSyncBeMaster();
2006
2007                 if(this.globalSelectionSyncAbility() === false)
2008                     return;
2009             }
2010
2011             NETDATA.globalSelectionSync.last_t = t;
2012             $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2013                 st.setSelection(t);
2014             });
2015         };
2016
2017         // stop syncing all charts to the given time
2018         this.globalSelectionSyncStop = function() {
2019             if(NETDATA.globalSelectionSync.slaves.length) {
2020                 if(this.debug === true)
2021                     this.log('sync: cleaning up...');
2022
2023                 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
2024                     if(st === that) {
2025                         if(that.debug === true)
2026                             st.log('sync: not adding me to sync stop');
2027                     }
2028                     else {
2029                         if(that.debug === true)
2030                             st.log('sync: removed slave from sync');
2031
2032                         st.clearSelection();
2033                     }
2034                 });
2035
2036                 NETDATA.globalSelectionSync.last_t = 0;
2037                 NETDATA.globalSelectionSync.slaves = [];
2038                 NETDATA.globalSelectionSync.state = null;
2039             }
2040
2041             this.clearSelection();
2042         };
2043
2044         this.setSelection = function(t) {
2045             if(typeof this.library.setSelection === 'function')
2046                 this.selected = (this.library.setSelection(this, t) === true);
2047             else
2048                 this.selected = true;
2049
2050             if(this.selected === true && this.debug === true)
2051                 this.log('selection set to ' + t.toString());
2052
2053             return this.selected;
2054         };
2055
2056         this.clearSelection = function() {
2057             if(this.selected === true) {
2058                 if(typeof this.library.clearSelection === 'function')
2059                     this.selected = (!(this.library.clearSelection(this) === true));
2060                 else
2061                     this.selected = false;
2062
2063                 if(this.selected === false && this.debug === true)
2064                     this.log('selection cleared');
2065
2066                 this.legendReset();
2067             }
2068
2069             return this.selected;
2070         };
2071
2072         // find if a timestamp (ms) is shown in the current chart
2073         this.timeIsVisible = function(t) {
2074             return (t >= this.data_after && t <= this.data_before);
2075         };
2076
2077         this.calculateRowForTime = function(t) {
2078             if(this.timeIsVisible(t) === false) return -1;
2079             return Math.floor((t - this.data_after) / this.data_update_every);
2080         };
2081
2082         // ----------------------------------------------------------------------------------------------------------------
2083
2084         // console logging
2085         this.log = function(msg) {
2086             console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
2087         };
2088
2089         this.pauseChart = function() {
2090             if(this.paused === false) {
2091                 if(this.debug === true)
2092                     this.log('pauseChart()');
2093
2094                 this.paused = true;
2095             }
2096         };
2097
2098         this.unpauseChart = function() {
2099             if(this.paused === true) {
2100                 if(this.debug === true)
2101                     this.log('unpauseChart()');
2102
2103                 this.paused = false;
2104             }
2105         };
2106
2107         this.resetChart = function(dont_clear_master, dont_update) {
2108             if(this.debug === true)
2109                 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
2110
2111             if(typeof dont_clear_master === 'undefined')
2112                 dont_clear_master = false;
2113
2114             if(typeof dont_update === 'undefined')
2115                 dont_update = false;
2116
2117             if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
2118                 if(this.debug === true)
2119                     this.log('resetChart() diverting to clearMaster().');
2120                 // this will call us back with master === true
2121                 NETDATA.globalPanAndZoom.clearMaster();
2122                 return;
2123             }
2124
2125             this.clearSelection();
2126
2127             this.tm.pan_and_zoom_seq = 0;
2128
2129             this.setMode('auto');
2130             this.current.force_update_at = 0;
2131             this.current.force_before_ms = null;
2132             this.current.force_after_ms = null;
2133             this.tm.last_autorefreshed = 0;
2134             this.paused = false;
2135             this.selected = false;
2136             this.enabled = true;
2137             // this.debug = false;
2138
2139             // do not update the chart here
2140             // or the chart will flip-flop when it is the master
2141             // of a selection sync and another chart becomes
2142             // the new master
2143
2144             if(dont_update !== true && this.isVisible() === true) {
2145                 this.updateChart();
2146             }
2147         };
2148
2149         this.updateChartPanOrZoom = function(after, before) {
2150             var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
2151             var ret = true;
2152
2153             if(this.debug === true)
2154                 this.log(logme);
2155
2156             if(before < after) {
2157                 if(this.debug === true)
2158                     this.log(logme + 'flipped parameters, rejecting it.');
2159
2160                 return false;
2161             }
2162
2163             if(typeof this.fixed_min_duration === 'undefined')
2164                 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
2165
2166             var min_duration = this.fixed_min_duration;
2167             var current_duration = Math.round(this.view_before - this.view_after);
2168
2169             // round the numbers
2170             after = Math.round(after);
2171             before = Math.round(before);
2172
2173             // align them to update_every
2174             // stretching them further away
2175             after -= after % this.data_update_every;
2176             before += this.data_update_every - (before % this.data_update_every);
2177
2178             // the final wanted duration
2179             var wanted_duration = before - after;
2180
2181             // to allow panning, accept just a point below our minimum
2182             if((current_duration - this.data_update_every) < min_duration)
2183                 min_duration = current_duration - this.data_update_every;
2184
2185             // we do it, but we adjust to minimum size and return false
2186             // when the wanted size is below the current and the minimum
2187             // and we zoom
2188             if(wanted_duration < current_duration && wanted_duration < min_duration) {
2189                 if(this.debug === true)
2190                     this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2191
2192                 min_duration = this.fixed_min_duration;
2193
2194                 var dt = (min_duration - wanted_duration) / 2;
2195                 before += dt;
2196                 after -= dt;
2197                 wanted_duration = before - after;
2198                 ret = false;
2199             }
2200
2201             var tolerance = this.data_update_every * 2;
2202             var movement = Math.abs(before - this.view_before);
2203
2204             if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2205                 if(this.debug === true)
2206                     this.log(logme + 'REJECTING UPDATE: current/min duration: ' + (current_duration / 1000).toString() + '/' + (this.fixed_min_duration / 1000).toString() + ', wanted duration: ' + (wanted_duration / 1000).toString() + ', duration diff: ' + (Math.round(Math.abs(current_duration - wanted_duration) / 1000)).toString() + ', movement: ' + (movement / 1000).toString() + ', tolerance: ' + (tolerance / 1000).toString() + ', returning: ' + false);
2207                 return false;
2208             }
2209
2210             if(this.current.name === 'auto') {
2211                 this.log(logme + 'caller called me with mode: ' + this.current.name);
2212                 this.setMode('pan');
2213             }
2214
2215             if(this.debug === true)
2216                 this.log(logme + 'ACCEPTING UPDATE: current/min duration: ' + (current_duration / 1000).toString() + '/' + (this.fixed_min_duration / 1000).toString() + ', wanted duration: ' + (wanted_duration / 1000).toString() + ', duration diff: ' + (Math.round(Math.abs(current_duration - wanted_duration) / 1000)).toString() + ', movement: ' + (movement / 1000).toString() + ', tolerance: ' + (tolerance / 1000).toString() + ', returning: ' + ret);
2217
2218             this.current.force_update_at = Date.now() + NETDATA.options.current.pan_and_zoom_delay;
2219             this.current.force_after_ms = after;
2220             this.current.force_before_ms = before;
2221             NETDATA.globalPanAndZoom.setMaster(this, after, before);
2222             return ret;
2223         };
2224
2225         this.legendFormatValue = function(value) {
2226             if(value === null || value === 'undefined') return '-';
2227             if(typeof value !== 'number') return value;
2228
2229             if(this.value_decimal_detail !== -1)
2230                 return (Math.round(value * this.value_decimal_detail) / this.value_decimal_detail).toLocaleString();
2231
2232             var abs = Math.abs(value);
2233             if(abs >= 1000) return (Math.round(value)).toLocaleString();
2234             if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
2235             if(abs >= 1   ) return (Math.round(value * 100) / 100).toLocaleString();
2236             if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
2237             return (Math.round(value * 10000) / 10000).toLocaleString();
2238         };
2239
2240         this.legendSetLabelValue = function(label, value) {
2241             var series = this.element_legend_childs.series[label];
2242             if(typeof series === 'undefined') return;
2243             if(series.value === null && series.user === null) return;
2244
2245             /*
2246             // this slows down firefox and edge significantly
2247             // since it requires to use innerHTML(), instead of innerText()
2248
2249             // if the value has not changed, skip DOM update
2250             //if(series.last === value) return;
2251
2252             var s, r;
2253             if(typeof value === 'number') {
2254                 var v = Math.abs(value);
2255                 s = r = this.legendFormatValue(value);
2256
2257                 if(typeof series.last === 'number') {
2258                     if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2259                     else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2260                     else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2261                 }
2262                 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2263
2264                 series.last = v;
2265             }
2266             else {
2267                 if(value === null)
2268                     s = r = '';
2269                 else
2270                     s = r = value;
2271
2272                 series.last = value;
2273             }
2274             */
2275
2276             var s = this.legendFormatValue(value);
2277
2278             // caching: do not update the update to show the same value again
2279             if(s === series.last_shown_value) return;
2280             series.last_shown_value = s;
2281
2282             if(series.value !== null) series.value.innerText = s;
2283             if(series.user !== null) series.user.innerText = s;
2284         };
2285
2286         this.__legendSetDateString = function(date) {
2287             if(date !== this.__last_shown_legend_date) {
2288                 this.element_legend_childs.title_date.innerText = date;
2289                 this.__last_shown_legend_date = date;
2290             }
2291         };
2292
2293         this.__legendSetTimeString = function(time) {
2294             if(time !== this.__last_shown_legend_time) {
2295                 this.element_legend_childs.title_time.innerText = time;
2296                 this.__last_shown_legend_time = time;
2297             }
2298         };
2299
2300         this.__legendSetUnitsString = function(units) {
2301             if(units !== this.__last_shown_legend_units) {
2302                 this.element_legend_childs.title_units.innerText = units;
2303                 this.__last_shown_legend_units = units;
2304             }
2305         };
2306
2307         this.legendSetDate = function(ms) {
2308             if(typeof ms !== 'number') {
2309                 this.legendShowUndefined();
2310                 return;
2311             }
2312
2313             var d = new Date(ms);
2314
2315             if(this.element_legend_childs.title_date)
2316                 this.__legendSetDateString(d.toLocaleDateString());
2317
2318             if(this.element_legend_childs.title_time)
2319                 this.__legendSetTimeString(d.toLocaleTimeString());
2320
2321             if(this.element_legend_childs.title_units)
2322                 this.__legendSetUnitsString(this.units)
2323         };
2324
2325         this.legendShowUndefined = function() {
2326             if(this.element_legend_childs.title_date)
2327                 this.__legendSetDateString(' ');
2328
2329             if(this.element_legend_childs.title_time)
2330                 this.__legendSetTimeString(this.chart.name);
2331
2332             if(this.element_legend_childs.title_units)
2333                 this.__legendSetUnitsString(' ');
2334
2335             if(this.data && this.element_legend_childs.series !== null) {
2336                 var labels = this.data.dimension_names;
2337                 var i = labels.length;
2338                 while(i--) {
2339                     var label = labels[i];
2340
2341                     if(typeof label === 'undefined') continue;
2342                     if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2343                     this.legendSetLabelValue(label, null);
2344                 }
2345             }
2346         };
2347
2348         this.legendShowLatestValues = function() {
2349             if(this.chart === null) return;
2350             if(this.selected) return;
2351
2352             if(this.data === null || this.element_legend_childs.series === null) {
2353                 this.legendShowUndefined();
2354                 return;
2355             }
2356
2357             var show_undefined = true;
2358             if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2359                 show_undefined = false;
2360
2361             if(show_undefined) {
2362                 this.legendShowUndefined();
2363                 return;
2364             }
2365
2366             this.legendSetDate(this.view_before);
2367
2368             var labels = this.data.dimension_names;
2369             var i = labels.length;
2370             while(i--) {
2371                 var label = labels[i];
2372
2373                 if(typeof label === 'undefined') continue;
2374                 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2375
2376                 if(show_undefined)
2377                     this.legendSetLabelValue(label, null);
2378                 else
2379                     this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2380             }
2381         };
2382
2383         this.legendReset = function() {
2384             this.legendShowLatestValues();
2385         };
2386
2387         // this should be called just ONCE per dimension per chart
2388         this._chartDimensionColor = function(label) {
2389             if(this.colors === null) this.chartColors();
2390
2391             if(typeof this.colors_assigned[label] === 'undefined') {
2392                 if(this.colors_available.length === 0) {
2393                     var len = NETDATA.themes.current.colors.length;
2394                     while(len--)
2395                         this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2396                 }
2397
2398                 this.colors_assigned[label] = this.colors_available.shift();
2399
2400                 if(this.debug === true)
2401                     this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2402             }
2403             else {
2404                 if(this.debug === true)
2405                     this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2406             }
2407
2408             this.colors.push(this.colors_assigned[label]);
2409             return this.colors_assigned[label];
2410         };
2411
2412         this.chartColors = function() {
2413             if(this.colors !== null) return this.colors;
2414
2415             this.colors = [];
2416             this.colors_available = [];
2417
2418             // add the standard colors
2419             var len = NETDATA.themes.current.colors.length;
2420             while(len--)
2421                 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2422
2423             // add the user supplied colors
2424             var c = $(this.element).data('colors');
2425             // this.log('read colors: ' + c);
2426             if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2427                 if(typeof c !== 'string') {
2428                     this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2429                 }
2430                 else {
2431                     c = c.split(' ');
2432                     var added = 0;
2433
2434                     while(added < 20) {
2435                         len = c.length;
2436                         while(len--) {
2437                             added++;
2438                             this.colors_available.unshift(c[len]);
2439                             // this.log('adding color: ' + c[len]);
2440                         }
2441                     }
2442                 }
2443             }
2444
2445             return this.colors;
2446         };
2447
2448         this.legendUpdateDOM = function() {
2449             var needed = false, dim, keys, len, i;
2450
2451             // check that the legend DOM is up to date for the downloaded dimensions
2452             if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2453                 // this.log('the legend does not have any series - requesting legend update');
2454                 needed = true;
2455             }
2456             else if(this.data === null) {
2457                 // this.log('the chart does not have any data - requesting legend update');
2458                 needed = true;
2459             }
2460             else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2461                 needed = true;
2462             }
2463             else {
2464                 var labels = this.data.dimension_names.toString();
2465                 if(labels !== this.element_legend_childs.series.labels_key) {
2466                     needed = true;
2467
2468                     if(this.debug === true)
2469                         this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2470                 }
2471             }
2472
2473             if(needed === false) {
2474                 // make sure colors available
2475                 this.chartColors();
2476
2477                 // do we have to update the current values?
2478                 // we do this, only when the visible chart is current
2479                 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2480                     if(this.debug === true)
2481                         this.log('chart is in latest position... updating values on legend...');
2482
2483                     //var labels = this.data.dimension_names;
2484                     //var i = labels.length;
2485                     //while(i--)
2486                     //  this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2487                 }
2488                 return;
2489             }
2490             if(this.colors === null) {
2491                 // this is the first time we update the chart
2492                 // let's assign colors to all dimensions
2493                 if(this.library.track_colors() === true) {
2494                     keys = Object.keys(this.chart.dimensions);
2495                     len = keys.length;
2496                     for(i = 0; i < len ;i++)
2497                         this._chartDimensionColor(this.chart.dimensions[keys[i]].name);
2498                 }
2499             }
2500             // we will re-generate the colors for the chart
2501             // based on the selected dimensions
2502             this.colors = null;
2503
2504             if(this.debug === true)
2505                 this.log('updating Legend DOM');
2506
2507             // mark all dimensions as invalid
2508             this.dimensions_visibility.invalidateAll();
2509
2510             var genLabel = function(state, parent, dim, name, count) {
2511                 var color = state._chartDimensionColor(name);
2512
2513                 var user_element = null;
2514                 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2515                 if(user_id === null)
2516                     user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2517                 if(user_id !== null) {
2518                     user_element = document.getElementById(user_id) || null;
2519                     if (user_element === null)
2520                         state.log('Cannot find element with id: ' + user_id);
2521                 }
2522
2523                 state.element_legend_childs.series[name] = {
2524                     name: document.createElement('span'),
2525                     value: document.createElement('span'),
2526                     user: user_element,
2527                     last: null,
2528                     last_shown_value: null
2529                 };
2530
2531                 var label = state.element_legend_childs.series[name];
2532
2533                 // create the dimension visibility tracking for this label
2534                 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2535
2536                 var rgb = NETDATA.colorHex2Rgb(color);
2537                 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2538                     + state.chart.chart_type
2539                     + '" style="background-color: '
2540                     + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2541                     + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>';
2542
2543                 var text = document.createTextNode(' ' + name);
2544                 label.name.appendChild(text);
2545
2546                 if(count > 0)
2547                     parent.appendChild(document.createElement('br'));
2548
2549                 parent.appendChild(label.name);
2550                 parent.appendChild(label.value);
2551             };
2552
2553             var content = document.createElement('div');
2554
2555             if(this.hasLegend()) {
2556                 this.element_legend_childs = {
2557                     content: content,
2558                     resize_handler: document.createElement('div'),
2559                     toolbox: document.createElement('div'),
2560                     toolbox_left: document.createElement('div'),
2561                     toolbox_right: document.createElement('div'),
2562                     toolbox_reset: document.createElement('div'),
2563                     toolbox_zoomin: document.createElement('div'),
2564                     toolbox_zoomout: document.createElement('div'),
2565                     toolbox_volume: document.createElement('div'),
2566                     title_date: document.createElement('span'),
2567                     title_time: document.createElement('span'),
2568                     title_units: document.createElement('span'),
2569                     perfect_scroller: document.createElement('div'),
2570                     series: {}
2571                 };
2572
2573                 this.element_legend.innerHTML = '';
2574
2575                 if(this.library.toolboxPanAndZoom !== null) {
2576
2577                     var get_pan_and_zoom_step = function(event) {
2578                         if (event.ctrlKey)
2579                             return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2580
2581                         else if (event.shiftKey)
2582                             return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2583
2584                         else if (event.altKey)
2585                             return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2586
2587                         else
2588                             return NETDATA.options.current.pan_and_zoom_factor;
2589                     };
2590
2591                     this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2592                     this.element.appendChild(this.element_legend_childs.toolbox);
2593
2594                     this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2595                     this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2596                     this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2597                     this.element_legend_childs.toolbox_left.onclick = function(e) {
2598                         e.preventDefault();
2599
2600                         var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2601                         var before = that.view_before - step;
2602                         var after = that.view_after - step;
2603                         if(after >= that.netdata_first)
2604                             that.library.toolboxPanAndZoom(that, after, before);
2605                     };
2606                     if(NETDATA.options.current.show_help === true)
2607                         $(this.element_legend_childs.toolbox_left).popover({
2608                         container: "body",
2609                         animation: false,
2610                         html: true,
2611                         trigger: 'hover',
2612                         placement: 'bottom',
2613                         delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2614                         title: 'Pan Left',
2615                         content: 'Pan the chart to the left. You can also <b>drag it</b> with your mouse or your finger (on touch devices).<br/><small>Help, can be disabled from the settings.</small>'
2616                     });
2617
2618
2619                     this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2620                     this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2621                     this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2622                     this.element_legend_childs.toolbox_reset.onclick = function(e) {
2623                         e.preventDefault();
2624                         NETDATA.resetAllCharts(that);
2625                     };
2626                     if(NETDATA.options.current.show_help === true)
2627                         $(this.element_legend_childs.toolbox_reset).popover({
2628                         container: "body",
2629                         animation: false,
2630                         html: true,
2631                         trigger: 'hover',
2632                         placement: 'bottom',
2633                         delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2634                         title: 'Chart Reset',
2635                         content: 'Reset all the charts to their default auto-refreshing state. You can also <b>double click</b> the chart contents with your mouse or your finger (on touch devices).<br/><small>Help, can be disabled from the settings.</small>'
2636                     });
2637                     
2638                     this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2639                     this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2640                     this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2641                     this.element_legend_childs.toolbox_right.onclick = function(e) {
2642                         e.preventDefault();
2643                         var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2644                         var before = that.view_before + step;
2645                         var after = that.view_after + step;
2646                         if(before <= that.netdata_last)
2647                             that.library.toolboxPanAndZoom(that, after, before);
2648                     };
2649                     if(NETDATA.options.current.show_help === true)
2650                         $(this.element_legend_childs.toolbox_right).popover({
2651                         container: "body",
2652                         animation: false,
2653                         html: true,
2654                         trigger: 'hover',
2655                         placement: 'bottom',
2656                         delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2657                         title: 'Pan Right',
2658                         content: 'Pan the chart to the right. You can also <b>drag it</b> with your mouse or your finger (on touch devices).<br/><small>Help, can be disabled from the settings.</small>'
2659                     });
2660
2661                     
2662                     this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2663                     this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2664                     this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2665                     this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2666                         e.preventDefault();
2667                         var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2668                         var before = that.view_before - dt;
2669                         var after = that.view_after + dt;
2670                         that.library.toolboxPanAndZoom(that, after, before);
2671                     };
2672                     if(NETDATA.options.current.show_help === true)
2673                         $(this.element_legend_childs.toolbox_zoomin).popover({
2674                         container: "body",
2675                         animation: false,
2676                         html: true,
2677                         trigger: 'hover',
2678                         placement: 'bottom',
2679                         delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2680                         title: 'Chart Zoom In',
2681                         content: 'Zoom in the chart. You can also press SHIFT and select an area of the chart to zoom in. On Chrome and Opera, you can press the SHIFT or the ALT keys and then use the mouse wheel to zoom in or out.<br/><small>Help, can be disabled from the settings.</small>'
2682                     });
2683                     
2684                     this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2685                     this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2686                     this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2687                     this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2688                         e.preventDefault();
2689                         var dt = (((that.view_before - that.view_after) / (1.0 - (get_pan_and_zoom_step(e) * 0.8)) - (that.view_before - that.view_after)) / 2);
2690                         var before = that.view_before + dt;
2691                         var after = that.view_after - dt;
2692
2693                         that.library.toolboxPanAndZoom(that, after, before);
2694                     };
2695                     if(NETDATA.options.current.show_help === true)
2696                         $(this.element_legend_childs.toolbox_zoomout).popover({
2697                         container: "body",
2698                         animation: false,
2699                         html: true,
2700                         trigger: 'hover',
2701                         placement: 'bottom',
2702                         delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2703                         title: 'Chart Zoom Out',
2704                         content: 'Zoom out the chart. On Chrome and Opera, you can also press the SHIFT or the ALT keys and then use the mouse wheel to zoom in or out.<br/><small>Help, can be disabled from the settings.</small>'
2705                     });
2706                     
2707                     //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2708                     //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2709                     //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2710                     //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2711                     //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2712                         //e.preventDefault();
2713                         //alert('clicked toolbox_volume on ' + that.id);
2714                     //}
2715                 }
2716                 else {
2717                     this.element_legend_childs.toolbox = null;
2718                     this.element_legend_childs.toolbox_left = null;
2719                     this.element_legend_childs.toolbox_reset = null;
2720                     this.element_legend_childs.toolbox_right = null;
2721                     this.element_legend_childs.toolbox_zoomin = null;
2722                     this.element_legend_childs.toolbox_zoomout = null;
2723                     this.element_legend_childs.toolbox_volume = null;
2724                 }
2725                 
2726                 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2727                 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2728                 this.element.appendChild(this.element_legend_childs.resize_handler);
2729                 if(NETDATA.options.current.show_help === true)
2730                     $(this.element_legend_childs.resize_handler).popover({
2731                     container: "body",
2732                     animation: false,
2733                     html: true,
2734                     trigger: 'hover',
2735                     placement: 'bottom',
2736                     delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2737                     title: 'Chart Resize',
2738                     content: 'Drag this point with your mouse or your finger (on touch devices), to resize the chart vertically. You can also <b>double click it</b> or <b>double tap it</b> to reset between 2 states: the default and the one that fits all the values.<br/><small>Help, can be disabled from the settings.</small>'
2739                 });
2740
2741                 // mousedown event
2742                 this.element_legend_childs.resize_handler.onmousedown =
2743                     function(e) {
2744                         that.resizeHandler(e);
2745                     };
2746
2747                 // touchstart event
2748                 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2749                     that.resizeHandler(e);
2750                 }, false);
2751
2752                 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2753                 this.element_legend.appendChild(this.element_legend_childs.title_date);
2754
2755                 this.element_legend.appendChild(document.createElement('br'));
2756
2757                 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2758                 this.element_legend.appendChild(this.element_legend_childs.title_time);
2759
2760                 this.element_legend.appendChild(document.createElement('br'));
2761
2762                 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2763                 this.element_legend.appendChild(this.element_legend_childs.title_units);
2764
2765                 this.element_legend.appendChild(document.createElement('br'));
2766
2767                 this.element_legend_childs.perfect_scroller.className = 'netdata-legend-series';
2768                 this.element_legend.appendChild(this.element_legend_childs.perfect_scroller);
2769
2770                 content.className = 'netdata-legend-series-content';
2771                 this.element_legend_childs.perfect_scroller.appendChild(content);
2772
2773                 if(NETDATA.options.current.show_help === true)
2774                     $(content).popover({
2775                     container: "body",
2776                     animation: false,
2777                     html: true,
2778                     trigger: 'hover',
2779                     placement: 'bottom',
2780                     title: 'Chart Legend',
2781                     delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2782                     content: 'You can click or tap on the values or the labels to select dimentions. By pressing SHIFT or CONTROL, you can enable or disable multiple dimensions.<br/><small>Help, can be disabled from the settings.</small>'
2783                 });
2784             }
2785             else {
2786                 this.element_legend_childs = {
2787                     content: content,
2788                     resize_handler: null,
2789                     toolbox: null,
2790                     toolbox_left: null,
2791                     toolbox_right: null,
2792                     toolbox_reset: null,
2793                     toolbox_zoomin: null,
2794                     toolbox_zoomout: null,
2795                     toolbox_volume: null,
2796                     title_date: null,
2797                     title_time: null,
2798                     title_units: null,
2799                     perfect_scroller: null,
2800                     series: {}
2801                 };
2802             }
2803
2804             if(this.data) {
2805                 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2806                 if(this.debug === true)
2807                     this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2808
2809                 for(i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2810                     genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2811                 }
2812             }
2813             else {
2814                 var tmp = [];
2815                 keys = Object.keys(this.chart.dimensions);
2816                 for(i = 0, len = keys.length; i < len ;i++) {
2817                     dim = keys[i];
2818                     tmp.push(this.chart.dimensions[dim].name);
2819                     genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2820                 }
2821                 this.element_legend_childs.series.labels_key = tmp.toString();
2822                 if(this.debug === true)
2823                     this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2824             }
2825
2826             // create a hidden div to be used for hidding
2827             // the original legend of the chart library
2828             var el = document.createElement('div');
2829             if(this.element_legend !== null)
2830                 this.element_legend.appendChild(el);
2831             el.style.display = 'none';
2832
2833             this.element_legend_childs.hidden = document.createElement('div');
2834             el.appendChild(this.element_legend_childs.hidden);
2835
2836             if(this.element_legend_childs.perfect_scroller !== null) {
2837                 Ps.initialize(this.element_legend_childs.perfect_scroller, {
2838                     wheelSpeed: 0.2,
2839                     wheelPropagation: true,
2840                     swipePropagation: true,
2841                     minScrollbarLength: null,
2842                     maxScrollbarLength: null,
2843                     useBothWheelAxes: false,
2844                     suppressScrollX: true,
2845                     suppressScrollY: false,
2846                     scrollXMarginOffset: 0,
2847                     scrollYMarginOffset: 0,
2848                     theme: 'default'
2849                 });
2850                 Ps.update(this.element_legend_childs.perfect_scroller);
2851             }
2852
2853             this.legendShowLatestValues();
2854         };
2855
2856         this.hasLegend = function() {
2857             if(typeof this.___hasLegendCache___ !== 'undefined')
2858                 return this.___hasLegendCache___;
2859
2860             var leg = false;
2861             if(this.library && this.library.legend(this) === 'right-side') {
2862                 var legend = $(this.element).data('legend') || 'yes';
2863                 if(legend === 'yes') leg = true;
2864             }
2865
2866             this.___hasLegendCache___ = leg;
2867             return leg;
2868         };
2869
2870         this.legendWidth = function() {
2871             return (this.hasLegend())?140:0;
2872         };
2873
2874         this.legendHeight = function() {
2875             return $(this.element).height();
2876         };
2877
2878         this.chartWidth = function() {
2879             return $(this.element).width() - this.legendWidth();
2880         };
2881
2882         this.chartHeight = function() {
2883             return $(this.element).height();
2884         };
2885
2886         this.chartPixelsPerPoint = function() {
2887             // force an options provided detail
2888             var px = this.pixels_per_point;
2889
2890             if(this.library && px < this.library.pixels_per_point(this))
2891                 px = this.library.pixels_per_point(this);
2892
2893             if(px < NETDATA.options.current.pixels_per_point)
2894                 px = NETDATA.options.current.pixels_per_point;
2895
2896             return px;
2897         };
2898
2899         this.needsRecreation = function() {
2900             return (
2901                     this.chart_created === true
2902                     && this.library
2903                     && this.library.autoresize() === false
2904                     && this.tm.last_resized < NETDATA.options.last_resized
2905                 );
2906         };
2907
2908         this.chartURL = function() {
2909             var after, before, points_multiplier = 1;
2910             if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2911                 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2912
2913                 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2914                 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2915                 this.view_after = after * 1000;
2916                 this.view_before = before * 1000;
2917
2918                 this.requested_padding = null;
2919                 points_multiplier = 1;
2920             }
2921             else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2922                 this.tm.pan_and_zoom_seq = 0;
2923
2924                 before = Math.round(this.current.force_before_ms / 1000);
2925                 after  = Math.round(this.current.force_after_ms / 1000);
2926                 this.view_after = after * 1000;
2927                 this.view_before = before * 1000;
2928
2929                 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2930                     this.requested_padding = Math.round((before - after) / 2);
2931                     after -= this.requested_padding;
2932                     before += this.requested_padding;
2933                     this.requested_padding *= 1000;
2934                     points_multiplier = 2;
2935                 }
2936
2937                 this.current.force_before_ms = null;
2938                 this.current.force_after_ms = null;
2939             }
2940             else {
2941                 this.tm.pan_and_zoom_seq = 0;
2942
2943                 before = this.before;
2944                 after  = this.after;
2945                 this.view_after = after * 1000;
2946                 this.view_before = before * 1000;
2947
2948                 this.requested_padding = null;
2949                 points_multiplier = 1;
2950             }
2951
2952             this.requested_after = after * 1000;
2953             this.requested_before = before * 1000;
2954
2955             this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2956
2957             // build the data URL
2958             this.data_url = this.host + this.chart.data_url;
2959             this.data_url += "&format="  + this.library.format();
2960             this.data_url += "&points="  + (this.data_points * points_multiplier).toString();
2961             this.data_url += "&group="   + this.method;
2962
2963             if(this.override_options !== null)
2964                 this.data_url += "&options=" + this.override_options.toString();
2965             else
2966                 this.data_url += "&options=" + this.library.options(this);
2967
2968             this.data_url += '|jsonwrap';
2969
2970             if(NETDATA.options.current.eliminate_zero_dimensions === true)
2971                 this.data_url += '|nonzero';
2972
2973             if(this.append_options !== null)
2974                 this.data_url += '|' + this.append_options.toString();
2975
2976             if(after)
2977                 this.data_url += "&after="  + after.toString();
2978
2979             if(before)
2980                 this.data_url += "&before=" + before.toString();
2981
2982             if(this.dimensions)
2983                 this.data_url += "&dimensions=" + this.dimensions;
2984
2985             if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2986                 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2987         };
2988
2989         this.redrawChart = function() {
2990             if(this.data !== null)
2991                 this.updateChartWithData(this.data);
2992         };
2993
2994         this.updateChartWithData = function(data) {
2995             if(this.debug === true)
2996                 this.log('updateChartWithData() called.');
2997
2998             // this may force the chart to be re-created
2999             resizeChart();
3000
3001             this.data = data;
3002             this.updates_counter++;
3003             this.updates_since_last_unhide++;
3004             this.updates_since_last_creation++;
3005
3006             var started = Date.now();
3007
3008             // if the result is JSON, find the latest update-every
3009             this.data_update_every = data.view_update_every * 1000;
3010             this.data_after = data.after * 1000;
3011             this.data_before = data.before * 1000;
3012             this.netdata_first = data.first_entry * 1000;
3013             this.netdata_last = data.last_entry * 1000;
3014             this.data_points = data.points;
3015             data.state = this;
3016
3017             if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
3018                 if(this.view_after < this.data_after) {
3019                     // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
3020                     this.view_after = this.data_after;
3021                 }
3022
3023                 if(this.view_before > this.data_before) {
3024                     // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
3025                     this.view_before = this.data_before;
3026                 }
3027             }
3028             else {
3029                 this.view_after = this.data_after;
3030                 this.view_before = this.data_before;
3031             }
3032
3033             if(this.debug === true) {
3034                 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
3035
3036                 if(this.current.force_after_ms)
3037                     this.log('STATUS: forced    : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
3038                 else
3039                     this.log('STATUS: forced    : unset');
3040
3041                 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
3042                 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
3043                 this.log('STATUS: rendered  : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
3044                 this.log('STATUS: points    : ' + (this.data_points).toString());
3045             }
3046
3047             if(this.data_points === 0) {
3048                 noDataToShow();
3049                 return;
3050             }
3051
3052             if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
3053                 if(this.debug === true)
3054                     this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
3055
3056                 this.chart_created = false;
3057             }
3058
3059             // check and update the legend
3060             this.legendUpdateDOM();
3061
3062             if(this.chart_created === true
3063                 && typeof this.library.update === 'function') {
3064
3065                 if(this.debug === true)
3066                     this.log('updating chart...');
3067
3068                 if(callChartLibraryUpdateSafely(data) === false)
3069                     return;
3070             }
3071             else {
3072                 if(this.debug === true)
3073                     this.log('creating chart...');
3074
3075                 if(callChartLibraryCreateSafely(data) === false)
3076                     return;
3077             }
3078             hideMessage();
3079             this.legendShowLatestValues();
3080             if(this.selected === true)
3081                 NETDATA.globalSelectionSync.stop();
3082
3083             // update the performance counters
3084             var now = Date.now();
3085             this.tm.last_updated = now;
3086
3087             // don't update last_autorefreshed if this chart is
3088             // forced to be updated with global PanAndZoom
3089             if(NETDATA.globalPanAndZoom.isActive())
3090                 this.tm.last_autorefreshed = 0;
3091             else {
3092                 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3093                     this.tm.last_autorefreshed = now - (now % this.data_update_every);
3094                 else
3095                     this.tm.last_autorefreshed = now;
3096             }
3097
3098             this.refresh_dt_ms = now - started;
3099             NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3100
3101             if(this.refresh_dt_element !== null)
3102                 this.refresh_dt_element.innerText = this.refresh_dt_ms.toString();
3103         };
3104
3105         this.updateChart = function(callback) {
3106             if(this.debug === true)
3107                 this.log('updateChart() called.');
3108
3109             if(this._updating === true) {
3110                 if(this.debug === true)
3111                     this.log('I am already updating...');
3112
3113                 if(typeof callback === 'function')
3114                     return callback();
3115
3116                 return;
3117             }
3118
3119             // due to late initialization of charts and libraries
3120             // we need to check this too
3121             if(this.enabled === false) {
3122                 if(this.debug === true)
3123                     this.log('I am not enabled');
3124
3125                 if(typeof callback === 'function')
3126                     return callback();
3127
3128                 return;
3129             }
3130
3131             if(canBeRendered() === false) {
3132                 if(typeof callback === 'function')
3133                     return callback();
3134
3135                 return;
3136             }
3137
3138             if(this.chart === null)
3139                 return this.getChart(function() {
3140                     return that.updateChart(callback);
3141                 });
3142
3143             if(this.library.initialized === false) {
3144                 if(this.library.enabled === true) {
3145                     return this.library.initialize(function () {
3146                         return that.updateChart(callback);
3147                     });
3148                 }
3149                 else {
3150                     error('chart library "' + this.library_name + '" is not available.');
3151
3152                     if(typeof callback === 'function')
3153                         return callback();
3154
3155                     return;
3156                 }
3157             }
3158
3159             this.clearSelection();
3160             this.chartURL();
3161
3162             if(this.debug === true)
3163                 this.log('updating from ' + this.data_url);
3164
3165             NETDATA.statistics.refreshes_total++;
3166             NETDATA.statistics.refreshes_active++;
3167
3168             if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3169                 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3170
3171             this._updating = true;
3172
3173             this.xhr = $.ajax( {
3174                 url: this.data_url,
3175                 cache: false,
3176                 async: true,
3177                 headers: {
3178                     'Cache-Control': 'no-cache, no-store',
3179                     'Pragma': 'no-cache'
3180                 },
3181                 xhrFields: { withCredentials: true } // required for the cookie
3182             })
3183             .done(function(data) {
3184                 that.xhr = undefined;
3185                 that.retries_on_data_failures = 0;
3186
3187                 if(that.debug === true)
3188                     that.log('data received. updating chart.');
3189
3190                 that.updateChartWithData(data);
3191             })
3192             .fail(function(msg) {
3193                 that.xhr = undefined;
3194
3195                 if(msg.statusText !== 'abort') {
3196                     that.retries_on_data_failures++;
3197                     if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3198                         // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3199                         that.retries_on_data_failures = 0;
3200                         error('data download failed for url: ' + that.data_url);
3201                     }
3202                     else {
3203                         that.tm.last_autorefreshed = Date.now();
3204                         // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3205                     }
3206                 }
3207             })
3208             .always(function() {
3209                 that.xhr = undefined;
3210
3211                 NETDATA.statistics.refreshes_active--;
3212                 that._updating = false;
3213
3214                 if(typeof callback === 'function')
3215                     return callback();
3216             });
3217         };
3218
3219         this.isVisible = function(nocache) {
3220             if(typeof nocache === 'undefined')
3221                 nocache = false;
3222
3223             // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3224
3225             // caching - we do not evaluate the charts visibility
3226             // if the page has not been scrolled since the last check
3227             if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3228                 return this.___isVisible___;
3229
3230             this.tm.last_visible_check = Date.now();
3231
3232             var wh = window.innerHeight;
3233             var x = this.element.getBoundingClientRect();
3234             var ret = 0;
3235             var tolerance = 0;
3236
3237             if(x.width === 0 || x.height === 0) {
3238                 hideChart();
3239                 this.___isVisible___ = false;
3240                 return this.___isVisible___;
3241             }
3242
3243             if(x.top < 0 && -x.top > x.height) {
3244                 // the chart is entirely above
3245                 ret = -x.top - x.height;
3246             }
3247             else if(x.top > wh) {
3248                 // the chart is entirely below
3249                 ret = x.top - wh;
3250             }
3251
3252             if(ret > tolerance) {
3253                 // the chart is too far
3254
3255                 hideChart();
3256                 this.___isVisible___ = false;
3257                 return this.___isVisible___;
3258             }
3259             else {
3260                 // the chart is inside or very close
3261
3262                 unhideChart();
3263                 this.___isVisible___ = true;
3264                 return this.___isVisible___;
3265             }
3266         };
3267
3268         this.isAutoRefreshable = function() {
3269             return (this.current.autorefresh);
3270         };
3271
3272         this.canBeAutoRefreshed = function() {
3273             var now = Date.now();
3274
3275             if(this.running === true) {
3276                 if(this.debug === true)
3277                     this.log('I am already running');
3278
3279                 return false;
3280             }
3281
3282             if(this.enabled === false) {
3283                 if(this.debug === true)
3284                     this.log('I am not enabled');
3285
3286                 return false;
3287             }
3288
3289             if(this.library === null || this.library.enabled === false) {
3290                 error('charting library "' + this.library_name + '" is not available');
3291                 if(this.debug === true)
3292                     this.log('My chart library ' + this.library_name + ' is not available');
3293
3294                 return false;
3295             }
3296
3297             if(this.isVisible() === false) {
3298                 if(NETDATA.options.debug.visibility === true || this.debug === true)
3299                     this.log('I am not visible');
3300
3301                 return false;
3302             }
3303
3304             if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3305                 if(this.debug === true)
3306                     this.log('timed force update detected - allowing this update');
3307
3308                 this.current.force_update_at = 0;
3309                 return true;
3310             }
3311
3312             if(this.isAutoRefreshable() === true) {
3313                 // allow the first update, even if the page is not visible
3314                 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3315                     if(NETDATA.options.debug.focus === true || this.debug === true)
3316                         this.log('canBeAutoRefreshed(): page does not have focus');
3317
3318                     return false;
3319                 }
3320
3321                 if(this.needsRecreation() === true) {
3322                     if(this.debug === true)
3323                         this.log('canBeAutoRefreshed(): needs re-creation.');
3324
3325                     return true;
3326                 }
3327
3328                 // options valid only for autoRefresh()
3329                 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3330                     if(NETDATA.globalPanAndZoom.isActive()) {
3331                         if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3332                             if(this.debug === true)
3333                                 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3334
3335                             return true;
3336                         }
3337                         else {
3338                             if(this.debug === true)
3339                                 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3340
3341                             return false;
3342                         }
3343                     }
3344
3345                     if(this.selected === true) {
3346                         if(this.debug === true)
3347                             this.log('canBeAutoRefreshed(): I have a selection in place.');
3348
3349                         return false;
3350                     }
3351
3352                     if(this.paused === true) {
3353                         if(this.debug === true)
3354                             this.log('canBeAutoRefreshed(): I am paused.');
3355
3356                         return false;
3357                     }
3358
3359                     if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3360                         if(this.debug === true)
3361                             this.log('canBeAutoRefreshed(): It is time to update me.');
3362
3363                         return true;
3364                     }
3365                 }
3366             }
3367
3368             return false;
3369         };
3370
3371         this.autoRefresh = function(callback) {
3372             if(this.canBeAutoRefreshed() === true && this.running === false) {
3373                 var state = this;
3374
3375                 state.running = true;
3376                 state.updateChart(function() {
3377                     state.running = false;
3378
3379                     if(typeof callback !== 'undefined')
3380                         return callback();
3381                 });
3382             }
3383             else {
3384                 if(typeof callback !== 'undefined')
3385                     return callback();
3386             }
3387         };
3388
3389         this._defaultsFromDownloadedChart = function(chart) {
3390             this.chart = chart;
3391             this.chart_url = chart.url;
3392             this.data_update_every = chart.update_every * 1000;
3393             this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3394             this.tm.last_info_downloaded = Date.now();
3395
3396             if(this.title === null)
3397                 this.title = chart.title;
3398
3399             if(this.units === null)
3400                 this.units = chart.units;
3401         };
3402
3403         // fetch the chart description from the netdata server
3404         this.getChart = function(callback) {
3405             this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3406             if(this.chart) {
3407                 this._defaultsFromDownloadedChart(this.chart);
3408
3409                 if(typeof callback === 'function')
3410                     return callback();
3411             }
3412             else {
3413                 this.chart_url = "/api/v1/chart?chart=" + this.id;
3414
3415                 if(this.debug === true)
3416                     this.log('downloading ' + this.chart_url);
3417
3418                 $.ajax( {
3419                     url:  this.host + this.chart_url,
3420                     cache: false,
3421                     async: true,
3422                     xhrFields: { withCredentials: true } // required for the cookie
3423                 })
3424                 .done(function(chart) {
3425                     chart.url = that.chart_url;
3426                     that._defaultsFromDownloadedChart(chart);
3427                     NETDATA.chartRegistry.add(that.host, that.id, chart);
3428                 })
3429                 .fail(function() {
3430                     NETDATA.error(404, that.chart_url);
3431                     error('chart not found on url "' + that.chart_url + '"');
3432                 })
3433                 .always(function() {
3434                     if(typeof callback === 'function')
3435                         return callback();
3436                 });
3437             }
3438         };
3439
3440         // ============================================================================================================
3441         // INITIALIZATION
3442
3443         init();
3444     };
3445
3446     NETDATA.resetAllCharts = function(state) {
3447         // first clear the global selection sync
3448         // to make sure no chart is in selected state
3449         state.globalSelectionSyncStop();
3450
3451         // there are 2 possibilities here
3452         // a. state is the global Pan and Zoom master
3453         // b. state is not the global Pan and Zoom master
3454         var master = true;
3455         if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3456             master = false;
3457
3458         // clear the global Pan and Zoom
3459         // this will also refresh the master
3460         // and unblock any charts currently mirroring the master
3461         NETDATA.globalPanAndZoom.clearMaster();
3462
3463         // if we were not the master, reset our status too
3464         // this is required because most probably the mouse
3465         // is over this chart, blocking it from auto-refreshing
3466         if(master === false && (state.paused === true || state.selected === true))
3467             state.resetChart();
3468     };
3469
3470     // get or create a chart state, given a DOM element
3471     NETDATA.chartState = function(element) {
3472         var state = $(element).data('netdata-state-object') || null;
3473         if(state === null) {
3474             state = new chartState(element);
3475             $(element).data('netdata-state-object', state);
3476         }
3477         return state;
3478     };
3479
3480     // ----------------------------------------------------------------------------------------------------------------
3481     // Library functions
3482
3483     // Load a script without jquery
3484     // This is used to load jquery - after it is loaded, we use jquery
3485     NETDATA._loadjQuery = function(callback) {
3486         if(typeof jQuery === 'undefined') {
3487             if(NETDATA.options.debug.main_loop === true)
3488                 console.log('loading ' + NETDATA.jQuery);
3489
3490             var script = document.createElement('script');
3491             script.type = 'text/javascript';
3492             script.async = true;
3493             script.src = NETDATA.jQuery;
3494
3495             // script.onabort = onError;
3496             script.onerror = function() { NETDATA.error(101, NETDATA.jQuery); };
3497             if(typeof callback === "function")
3498                 script.onload = callback;
3499
3500             var s = document.getElementsByTagName('script')[0];
3501             s.parentNode.insertBefore(script, s);
3502         }
3503         else if(typeof callback === "function")
3504             return callback();
3505     };
3506
3507     NETDATA._loadCSS = function(filename) {
3508         // don't use jQuery here
3509         // styles are loaded before jQuery
3510         // to eliminate showing an unstyled page to the user
3511
3512         var fileref = document.createElement("link");
3513         fileref.setAttribute("rel", "stylesheet");
3514         fileref.setAttribute("type", "text/css");
3515         fileref.setAttribute("href", filename);
3516
3517         if (typeof fileref !== 'undefined')
3518             document.getElementsByTagName("head")[0].appendChild(fileref);
3519     };
3520
3521     NETDATA.colorHex2Rgb = function(hex) {
3522         // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3523         var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3524             hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3525             return r + r + g + g + b + b;
3526         });
3527
3528         var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3529         return result ? {
3530             r: parseInt(result[1], 16),
3531             g: parseInt(result[2], 16),
3532             b: parseInt(result[3], 16)
3533         } : null;
3534     };
3535
3536     NETDATA.colorLuminance = function(hex, lum) {
3537         // validate hex string
3538         hex = String(hex).replace(/[^0-9a-f]/gi, '');
3539         if (hex.length < 6)
3540             hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3541
3542         lum = lum || 0;
3543
3544         // convert to decimal and change luminosity
3545         var rgb = "#", c, i;
3546         for (i = 0; i < 3; i++) {
3547             c = parseInt(hex.substr(i*2,2), 16);
3548             c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3549             rgb += ("00"+c).substr(c.length);
3550         }
3551
3552         return rgb;
3553     };
3554
3555     NETDATA.guid = function() {
3556         function s4() {
3557             return Math.floor((1 + Math.random()) * 0x10000)
3558                     .toString(16)
3559                     .substring(1);
3560             }
3561
3562             return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3563     };
3564
3565     NETDATA.zeropad = function(x) {
3566         if(x > -10 && x < 10) return '0' + x.toString();
3567         else return x.toString();
3568     };
3569
3570     // user function to signal us the DOM has been
3571     // updated.
3572     NETDATA.updatedDom = function() {
3573         NETDATA.options.updated_dom = true;
3574     };
3575
3576     NETDATA.ready = function(callback) {
3577         NETDATA.options.pauseCallback = callback;
3578     };
3579
3580     NETDATA.pause = function(callback) {
3581         if(typeof callback === 'function') {
3582             if (NETDATA.options.pause === true)
3583                 return callback();
3584             else
3585                 NETDATA.options.pauseCallback = callback;
3586         }
3587     };
3588
3589     NETDATA.unpause = function() {
3590         NETDATA.options.pauseCallback = null;
3591         NETDATA.options.updated_dom = true;
3592         NETDATA.options.pause = false;
3593     };
3594
3595     // ----------------------------------------------------------------------------------------------------------------
3596
3597     // this is purely sequential charts refresher
3598     // it is meant to be autonomous
3599     NETDATA.chartRefresherNoParallel = function(index) {
3600         if(NETDATA.options.debug.main_loop === true)
3601             console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3602
3603         if(NETDATA.options.updated_dom === true) {
3604             // the dom has been updated
3605             // get the dom parts again
3606             NETDATA.parseDom(NETDATA.chartRefresher);
3607             return;
3608         }
3609         if(index >= NETDATA.options.targets.length) {
3610             if(NETDATA.options.debug.main_loop === true)
3611                 console.log('waiting to restart main loop...');
3612
3613             NETDATA.options.auto_refresher_fast_weight = 0;
3614
3615             setTimeout(function() {
3616                 NETDATA.chartRefresher();
3617             }, NETDATA.options.current.idle_between_loops);
3618         }
3619         else {
3620             var state = NETDATA.options.targets[index];
3621
3622             if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3623                 if(NETDATA.options.debug.main_loop === true)
3624                     console.log('fast rendering...');
3625
3626                 state.autoRefresh(function() {
3627                     NETDATA.chartRefresherNoParallel(++index);
3628                 });
3629             }
3630             else {
3631                 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3632                 NETDATA.options.auto_refresher_fast_weight = 0;
3633
3634                 setTimeout(function() {
3635                     state.autoRefresh(function() {
3636                         NETDATA.chartRefresherNoParallel(++index);
3637                     });
3638                 }, NETDATA.options.current.idle_between_charts);
3639             }
3640         }
3641     };
3642
3643     NETDATA.chartRefresherWaitTime = function() {
3644         return NETDATA.options.current.idle_parallel_loops;
3645     };
3646
3647     // the default refresher
3648     NETDATA.chartRefresher = function() {
3649         // console.log('auto-refresher...');
3650
3651         if(NETDATA.options.pause === true) {
3652             // console.log('auto-refresher is paused');
3653             setTimeout(NETDATA.chartRefresher,
3654                 NETDATA.chartRefresherWaitTime());
3655             return;
3656         }
3657
3658         if(typeof NETDATA.options.pauseCallback === 'function') {
3659             // console.log('auto-refresher is calling pauseCallback');
3660             NETDATA.options.pause = true;
3661             NETDATA.options.pauseCallback();
3662             NETDATA.chartRefresher();
3663             return;
3664         }
3665
3666         if(NETDATA.options.current.parallel_refresher === false) {
3667             // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3668             NETDATA.chartRefresherNoParallel(0);
3669             return;
3670         }
3671
3672         if(NETDATA.options.updated_dom === true) {
3673             // the dom has been updated
3674             // get the dom parts again
3675             // console.log('auto-refresher is calling parseDom()');
3676             NETDATA.parseDom(NETDATA.chartRefresher);
3677             return;
3678         }
3679
3680         var parallel = [];
3681         var targets = NETDATA.options.targets;
3682         var len = targets.length;
3683         var state;
3684         while(len--) {
3685             state = targets[len];
3686             if(state.isVisible() === false || state.running === true)
3687                 continue;
3688
3689             if(state.library.initialized === false) {
3690                 if(state.library.enabled === true) {
3691                     state.library.initialize(NETDATA.chartRefresher);
3692                     return;
3693                 }
3694                 else {
3695                     state.error('chart library "' + state.library_name + '" is not enabled.');
3696                 }
3697             }
3698
3699             parallel.unshift(state);
3700         }
3701
3702         if(parallel.length > 0) {
3703             // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3704             // this will execute the jobs in parallel
3705             $(parallel).each(function() {
3706                 this.autoRefresh();
3707             })
3708         }
3709         //else {
3710         //    console.log('auto-refresher nothing to do');
3711         //}
3712
3713         // run the next refresh iteration
3714         setTimeout(NETDATA.chartRefresher,
3715             NETDATA.chartRefresherWaitTime());
3716     };
3717
3718     NETDATA.parseDom = function(callback) {
3719         NETDATA.options.last_page_scroll = Date.now();
3720         NETDATA.options.updated_dom = false;
3721
3722         var targets = $('div[data-netdata]'); //.filter(':visible');
3723
3724         if(NETDATA.options.debug.main_loop === true)
3725             console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3726
3727         NETDATA.options.targets = [];
3728         var len = targets.length;
3729         while(len--) {
3730             // the initialization will take care of sizing
3731             // and the "loading..." message
3732             NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3733         }
3734
3735         if(typeof callback === 'function')
3736             return callback();
3737     };
3738
3739     // this is the main function - where everything starts
3740     NETDATA.start = function() {
3741         // this should be called only once
3742
3743         NETDATA.options.page_is_visible = true;
3744
3745         $(window).blur(function() {
3746             if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3747                 NETDATA.options.page_is_visible = false;
3748                 if(NETDATA.options.debug.focus === true)
3749                     console.log('Lost Focus!');
3750             }
3751         });
3752
3753         $(window).focus(function() {
3754             if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3755                 NETDATA.options.page_is_visible = true;
3756                 if(NETDATA.options.debug.focus === true)
3757                     console.log('Focus restored!');
3758             }
3759         });
3760
3761         if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3762             if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3763                 NETDATA.options.page_is_visible = false;
3764                 if(NETDATA.options.debug.focus === true)
3765                     console.log('Document has no focus!');
3766             }
3767         }
3768
3769         // bootstrap tab switching
3770         $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3771
3772         // bootstrap modal switching
3773         var $modal = $('.modal');
3774         $modal.on('hidden.bs.modal', NETDATA.onscroll);
3775         $modal.on('shown.bs.modal', NETDATA.onscroll);
3776
3777         // bootstrap collapse switching
3778         var $collapse = $('.collapse');
3779         $collapse.on('hidden.bs.collapse', NETDATA.onscroll);
3780         $collapse.on('shown.bs.collapse', NETDATA.onscroll);
3781
3782         NETDATA.parseDom(NETDATA.chartRefresher);
3783
3784         // Alarms initialization
3785         setTimeout(NETDATA.alarms.init, 1000);
3786
3787         // Registry initialization
3788         setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3789
3790         if(typeof netdataCallback === 'function')
3791             netdataCallback();
3792     };
3793
3794     // ----------------------------------------------------------------------------------------------------------------
3795     // peity
3796
3797     NETDATA.peityInitialize = function(callback) {
3798         if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3799             $.ajax({
3800                 url: NETDATA.peity_js,
3801                 cache: true,
3802                 dataType: "script",
3803                 xhrFields: { withCredentials: true } // required for the cookie
3804             })
3805             .done(function() {
3806                 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3807             })
3808             .fail(function() {
3809                 NETDATA.chartLibraries.peity.enabled = false;
3810                 NETDATA.error(100, NETDATA.peity_js);
3811             })
3812             .always(function() {
3813                 if(typeof callback === "function")
3814                     return callback();
3815             });
3816         }
3817         else {
3818             NETDATA.chartLibraries.peity.enabled = false;
3819             if(typeof callback === "function")
3820                 return callback();
3821         }
3822     };
3823
3824     NETDATA.peityChartUpdate = function(state, data) {
3825         state.peity_instance.innerHTML = data.result;
3826
3827         if(state.peity_options.stroke !== state.chartColors()[0]) {
3828             state.peity_options.stroke = state.chartColors()[0];
3829             if(state.chart.chart_type === 'line')
3830                 state.peity_options.fill = NETDATA.themes.current.background;
3831             else
3832                 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3833         }
3834
3835         $(state.peity_instance).peity('line', state.peity_options);
3836         return true;
3837     };
3838
3839     NETDATA.peityChartCreate = function(state, data) {
3840         state.peity_instance = document.createElement('div');
3841         state.element_chart.appendChild(state.peity_instance);
3842
3843         var self = $(state.element);
3844         state.peity_options = {
3845             stroke: NETDATA.themes.current.foreground,
3846             strokeWidth: self.data('peity-strokewidth') || 1,
3847             width: state.chartWidth(),
3848             height: state.chartHeight(),
3849             fill: NETDATA.themes.current.foreground
3850         };
3851
3852         NETDATA.peityChartUpdate(state, data);
3853         return true;
3854     };
3855
3856     // ----------------------------------------------------------------------------------------------------------------
3857     // sparkline
3858
3859     NETDATA.sparklineInitialize = function(callback) {
3860         if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3861             $.ajax({
3862                 url: NETDATA.sparkline_js,
3863                 cache: true,
3864                 dataType: "script",
3865                 xhrFields: { withCredentials: true } // required for the cookie
3866             })
3867             .done(function() {
3868                 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3869             })
3870             .fail(function() {
3871                 NETDATA.chartLibraries.sparkline.enabled = false;
3872                 NETDATA.error(100, NETDATA.sparkline_js);
3873             })
3874             .always(function() {
3875                 if(typeof callback === "function")
3876                     return callback();
3877             });
3878         }
3879         else {
3880             NETDATA.chartLibraries.sparkline.enabled = false;
3881             if(typeof callback === "function")
3882                 return callback();
3883         }
3884     };
3885
3886     NETDATA.sparklineChartUpdate = function(state, data) {
3887         state.sparkline_options.width = state.chartWidth();
3888         state.sparkline_options.height = state.chartHeight();
3889
3890         $(state.element_chart).sparkline(data.result, state.sparkline_options);
3891         return true;
3892     };
3893
3894     NETDATA.sparklineChartCreate = function(state, data) {
3895         var self = $(state.element);
3896         var type = self.data('sparkline-type') || 'line';
3897         var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3898         var fillColor = self.data('sparkline-fillcolor') || ((state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance));
3899         var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3900         var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3901         var composite = self.data('sparkline-composite') || undefined;
3902         var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3903         var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3904         var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3905         var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3906         var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3907         var spotColor = self.data('sparkline-spotcolor') || undefined;
3908         var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3909         var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3910         var spotRadius = self.data('sparkline-spotradius') || undefined;
3911         var valueSpots = self.data('sparkline-valuespots') || undefined;
3912         var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3913         var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3914         var lineWidth = self.data('sparkline-linewidth') || undefined;
3915         var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3916         var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3917         var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3918         var xvalues = self.data('sparkline-xvalues') || undefined;
3919         var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3920         var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3921         var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3922         var disableInteraction = self.data('sparkline-disableinteraction') || false;
3923         var disableTooltips = self.data('sparkline-disabletooltips') || false;
3924         var disableHighlight = self.data('sparkline-disablehighlight') || false;
3925         var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3926         var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3927         var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3928         var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3929         var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3930         var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3931         var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3932         var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3933         var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3934         var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3935         var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3936         var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3937         var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3938         var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3939         var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3940         var animatedZooms = self.data('sparkline-animatedzooms') || false;
3941
3942         if(spotColor === 'disable') spotColor='';
3943         if(minSpotColor === 'disable') minSpotColor='';
3944         if(maxSpotColor === 'disable') maxSpotColor='';
3945
3946         // state.log('sparkline type ' + type + ', lineColor: ' + lineColor + ', fillColor: ' + fillColor);
3947
3948         state.sparkline_options = {
3949             type: type,
3950             lineColor: lineColor,
3951             fillColor: fillColor,
3952             chartRangeMin: chartRangeMin,
3953             chartRangeMax: chartRangeMax,
3954             composite: composite,
3955             enableTagOptions: enableTagOptions,
3956             tagOptionPrefix: tagOptionPrefix,
3957             tagValuesAttribute: tagValuesAttribute,
3958             disableHiddenCheck: disableHiddenCheck,
3959             defaultPixelsPerValue: defaultPixelsPerValue,
3960             spotColor: spotColor,
3961             minSpotColor: minSpotColor,
3962             maxSpotColor: maxSpotColor,
3963             spotRadius: spotRadius,
3964             valueSpots: valueSpots,
3965             highlightSpotColor: highlightSpotColor,
3966             highlightLineColor: highlightLineColor,
3967             lineWidth: lineWidth,
3968             normalRangeMin: normalRangeMin,
3969             normalRangeMax: normalRangeMax,
3970             drawNormalOnTop: drawNormalOnTop,
3971             xvalues: xvalues,
3972             chartRangeClip: chartRangeClip,
3973             chartRangeMinX: chartRangeMinX,
3974             chartRangeMaxX: chartRangeMaxX,
3975             disableInteraction: disableInteraction,
3976             disableTooltips: disableTooltips,
3977             disableHighlight: disableHighlight,
3978             highlightLighten: highlightLighten,
3979             highlightColor: highlightColor,
3980             tooltipContainer: tooltipContainer,
3981             tooltipClassname: tooltipClassname,
3982             tooltipChartTitle: state.title,
3983             tooltipFormat: tooltipFormat,
3984             tooltipPrefix: tooltipPrefix,
3985             tooltipSuffix: tooltipSuffix,
3986             tooltipSkipNull: tooltipSkipNull,
3987             tooltipValueLookups: tooltipValueLookups,
3988             tooltipFormatFieldlist: tooltipFormatFieldlist,
3989             tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3990             numberFormatter: numberFormatter,
3991             numberDigitGroupSep: numberDigitGroupSep,
3992             numberDecimalMark: numberDecimalMark,
3993             numberDigitGroupCount: numberDigitGroupCount,
3994             animatedZooms: animatedZooms,
3995             width: state.chartWidth(),
3996             height: state.chartHeight()
3997         };
3998
3999         $(state.element_chart).sparkline(data.result, state.sparkline_options);
4000         return true;
4001     };
4002
4003     // ----------------------------------------------------------------------------------------------------------------
4004     // dygraph
4005
4006     NETDATA.dygraph = {
4007         smooth: false
4008     };
4009
4010     NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
4011         if(after < state.netdata_first)
4012             after = state.netdata_first;
4013
4014         if(before > state.netdata_last)
4015             before = state.netdata_last;
4016
4017         state.setMode('zoom');
4018         state.globalSelectionSyncStop();
4019         state.globalSelectionSyncDelay();
4020         state.dygraph_user_action = true;
4021         state.dygraph_force_zoom = true;
4022         state.updateChartPanOrZoom(after, before);
4023         NETDATA.globalPanAndZoom.setMaster(state, after, before);
4024     };
4025
4026     NETDATA.dygraphSetSelection = function(state, t) {
4027         if(typeof state.dygraph_instance !== 'undefined') {
4028             var r = state.calculateRowForTime(t);
4029             if(r !== -1)
4030                 state.dygraph_instance.setSelection(r);
4031             else {
4032                 state.dygraph_instance.clearSelection();
4033                 state.legendShowUndefined();
4034             }
4035         }
4036
4037         return true;
4038     };
4039
4040     NETDATA.dygraphClearSelection = function(state) {
4041         if(typeof state.dygraph_instance !== 'undefined') {
4042             state.dygraph_instance.clearSelection();
4043         }
4044         return true;
4045     };
4046
4047     NETDATA.dygraphSmoothInitialize = function(callback) {
4048         $.ajax({
4049             url: NETDATA.dygraph_smooth_js,
4050             cache: true,
4051             dataType: "script",
4052             xhrFields: { withCredentials: true } // required for the cookie
4053         })
4054         .done(function() {
4055             NETDATA.dygraph.smooth = true;
4056             smoothPlotter.smoothing = 0.3;
4057         })
4058         .fail(function() {
4059             NETDATA.dygraph.smooth = false;
4060         })
4061         .always(function() {
4062             if(typeof callback === "function")
4063                 return callback();
4064         });
4065     };
4066
4067     NETDATA.dygraphInitialize = function(callback) {
4068         if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
4069             $.ajax({
4070                 url: NETDATA.dygraph_js,
4071                 cache: true,
4072                 dataType: "script",
4073                 xhrFields: { withCredentials: true } // required for the cookie
4074             })
4075             .done(function() {
4076                 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
4077             })
4078             .fail(function() {
4079                 NETDATA.chartLibraries.dygraph.enabled = false;
4080                 NETDATA.error(100, NETDATA.dygraph_js);
4081             })
4082             .always(function() {
4083                 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4084                     NETDATA.dygraphSmoothInitialize(callback);
4085                 else if(typeof callback === "function")
4086                     return callback();
4087             });
4088         }
4089         else {
4090             NETDATA.chartLibraries.dygraph.enabled = false;
4091             if(typeof callback === "function")
4092                 return callback();
4093         }
4094     };
4095
4096     NETDATA.dygraphChartUpdate = function(state, data) {
4097         var dygraph = state.dygraph_instance;
4098
4099         if(typeof dygraph === 'undefined')
4100             return NETDATA.dygraphChartCreate(state, data);
4101
4102         // when the chart is not visible, and hidden
4103         // if there is a window resize, dygraph detects
4104         // its element size as 0x0.
4105         // this will make it re-appear properly
4106
4107         if(state.tm.last_unhidden > state.dygraph_last_rendered)
4108             dygraph.resize();
4109
4110         var options = {
4111                 file: data.result.data,
4112                 colors: state.chartColors(),
4113                 labels: data.result.labels,
4114                 labelsDivWidth: state.chartWidth() - 70,
4115                 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4116         };
4117
4118         if(state.dygraph_force_zoom === true) {
4119             if(NETDATA.options.debug.dygraph === true || state.debug === true)
4120                 state.log('dygraphChartUpdate() forced zoom update');
4121
4122             options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4123             options.isZoomedIgnoreProgrammaticZoom = true;
4124             state.dygraph_force_zoom = false;
4125         }
4126         else if(state.current.name !== 'auto') {
4127             if(NETDATA.options.debug.dygraph === true || state.debug === true)
4128                 state.log('dygraphChartUpdate() loose update');
4129         }
4130         else {
4131             if(NETDATA.options.debug.dygraph === true || state.debug === true)
4132                 state.log('dygraphChartUpdate() strict update');
4133
4134             options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4135             options.isZoomedIgnoreProgrammaticZoom = true;
4136         }
4137
4138         options.valueRange = state.dygraph_options.valueRange;
4139
4140         var oldMax = null, oldMin = null;
4141         if(state.__commonMin !== null) {
4142             state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4143             oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4144         }
4145         if(state.__commonMax !== null) {
4146             state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4147             oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4148         }
4149
4150         if(state.dygraph_smooth_eligible === true) {
4151             if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4152                 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4153                 NETDATA.dygraphChartCreate(state, data);
4154                 return;
4155             }
4156         }
4157
4158         dygraph.updateOptions(options);
4159
4160         var redraw = false;
4161         if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4162             state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4163             options.valueRange[0] = NETDATA.commonMin.get(state);
4164             redraw = true;
4165         }
4166         if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4167             state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4168             options.valueRange[1] = NETDATA.commonMax.get(state);
4169             redraw = true;
4170         }
4171
4172         if(redraw === true) {
4173             // state.log('forcing redraw to adapt to common- min/max');
4174             dygraph.updateOptions(options);
4175         }
4176
4177         state.dygraph_last_rendered = Date.now();
4178         return true;
4179     };
4180
4181     NETDATA.dygraphChartCreate = function(state, data) {
4182         if(NETDATA.options.debug.dygraph === true || state.debug === true)
4183             state.log('dygraphChartCreate()');
4184
4185         var self = $(state.element);
4186
4187         var chart_type = state.chart.chart_type;
4188         if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4189         chart_type = self.data('dygraph-type') || chart_type;
4190
4191         var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state));
4192         smooth = self.data('dygraph-smooth') || smooth;
4193
4194         if(NETDATA.dygraph.smooth === false)
4195             smooth = false;
4196
4197         var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7);
4198         var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
4199
4200         state.dygraph_options = {
4201             colors: self.data('dygraph-colors') || state.chartColors(),
4202
4203             // leave a few pixels empty on the right of the chart
4204             rightGap: self.data('dygraph-rightgap') || 5,
4205             showRangeSelector: self.data('dygraph-showrangeselector') || false,
4206             showRoller: self.data('dygraph-showroller') || false,
4207
4208             title: self.data('dygraph-title') || state.title,
4209             titleHeight: self.data('dygraph-titleheight') || 19,
4210
4211             legend: self.data('dygraph-legend') || 'always', // we need this to get selection events
4212             labels: data.result.labels,
4213             labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
4214             labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
4215             labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
4216             labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
4217             labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
4218             labelsKMB: false,
4219             labelsKMG2: false,
4220             showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
4221             hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
4222
4223             includeZero: self.data('dygraph-includezero') || (chart_type === 'stacked'),
4224             xRangePad: self.data('dygraph-xrangepad') || 0,
4225             yRangePad: self.data('dygraph-yrangepad') || 1,
4226
4227             valueRange: self.data('dygraph-valuerange') || [ null, null ],
4228
4229             ylabel: state.units,
4230             yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
4231
4232             // the function to plot the chart
4233             plotter: null,
4234
4235             // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
4236             strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
4237             strokePattern: self.data('dygraph-strokepattern') || undefined,
4238
4239             // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
4240             // i.e. there is a missing point on either side of it. This also controls the size of those dots.
4241             drawPoints: self.data('dygraph-drawpoints') || false,
4242
4243             // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
4244             drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
4245
4246             connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
4247             pointSize: self.data('dygraph-pointsize') || 1,
4248
4249             // enabling this makes the chart with little square lines
4250             stepPlot: self.data('dygraph-stepplot') || false,
4251
4252             // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
4253             strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
4254             strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
4255
4256             fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked'),
4257             fillAlpha: self.data('dygraph-fillalpha') || ((chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area),
4258             stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked'),
4259             stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
4260
4261             drawAxis: self.data('dygraph-drawaxis') || true,
4262             axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
4263             axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
4264             axisLineWidth: self.data('dygraph-axislinewidth') || 1.0,
4265
4266             drawGrid: self.data('dygraph-drawgrid') || true,
4267             gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4268             gridLineWidth: self.data('dygraph-gridlinewidth') || 1.0,
4269             gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4270
4271             maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4272             sigFigs: self.data('dygraph-sigfigs') || null,
4273             digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4274             valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4275
4276             highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4277             highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4278             highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4279
4280             pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4281             visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4282             axes: {
4283                 x: {
4284                     pixelsPerLabel: 50,
4285                     ticker: Dygraph.dateTicker,
4286                     axisLabelFormatter: function (d, gran) {
4287                         void(gran);
4288                         return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4289                     },
4290                     valueFormatter: function (ms) {
4291                         void(ms);
4292                         //var d = new Date(ms);
4293                         //return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4294
4295                         // no need to return anything here
4296                         return ' ';
4297
4298                     }
4299                 },
4300                 y: {
4301                     pixelsPerLabel: 15,
4302                     valueFormatter: function (x) {
4303                         // we format legends with the state object
4304                         // no need to do anything here
4305                         // return (Math.round(x*100) / 100).toLocaleString();
4306                         // return state.legendFormatValue(x);
4307                         return x;
4308                     }
4309                 }
4310             },
4311             legendFormatter: function(data) {
4312                 var elements = state.element_legend_childs;
4313
4314                 // if the hidden div is not there
4315                 // we are not managing the legend
4316                 if(elements.hidden === null) return;
4317
4318                 if (typeof data.x !== 'undefined') {
4319                     state.legendSetDate(data.x);
4320                     var i = data.series.length;
4321                     while(i--) {
4322                         var series = data.series[i];
4323                         if(series.isVisible === true)
4324                             state.legendSetLabelValue(series.label, series.y);
4325                         else
4326                             state.legendSetLabelValue(series.label, null);
4327                     }
4328                 }
4329
4330                 return '';
4331             },
4332             drawCallback: function(dygraph, is_initial) {
4333                 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4334                     state.dygraph_user_action = false;
4335
4336                     var x_range = dygraph.xAxisRange();
4337                     var after = Math.round(x_range[0]);
4338                     var before = Math.round(x_range[1]);
4339
4340                     if(NETDATA.options.debug.dygraph === true)
4341                         state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4342
4343                     if(before <= state.netdata_last && after >= state.netdata_first)
4344                         state.updateChartPanOrZoom(after, before);
4345                 }
4346             },
4347             zoomCallback: function(minDate, maxDate, yRanges) {
4348                 void(yRanges);
4349
4350                 if(NETDATA.options.debug.dygraph === true)
4351                     state.log('dygraphZoomCallback()');
4352
4353                 state.globalSelectionSyncStop();
4354                 state.globalSelectionSyncDelay();
4355                 state.setMode('zoom');
4356
4357                 // refresh it to the greatest possible zoom level
4358                 state.dygraph_user_action = true;
4359                 state.dygraph_force_zoom = true;
4360                 state.updateChartPanOrZoom(minDate, maxDate);
4361             },
4362             highlightCallback: function(event, x, points, row, seriesName) {
4363                 void(seriesName);
4364
4365                 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4366                     state.log('dygraphHighlightCallback()');
4367
4368                 state.pauseChart();
4369
4370                 // there is a bug in dygraph when the chart is zoomed enough
4371                 // the time it thinks is selected is wrong
4372                 // here we calculate the time t based on the row number selected
4373                 // which is ok
4374                 var t = state.data_after + row * state.data_update_every;
4375                 // console.log('row = ' + row + ', x = ' + x + ', t = ' + t + ' ' + ((t === x)?'SAME':(Math.abs(x-t)<=state.data_update_every)?'SIMILAR':'DIFFERENT') + ', rows in db: ' + state.data_points + ' visible(x) = ' + state.timeIsVisible(x) + ' visible(t) = ' + state.timeIsVisible(t) + ' r(x) = ' + state.calculateRowForTime(x) + ' r(t) = ' + state.calculateRowForTime(t) + ' range: ' + state.data_after + ' - ' + state.data_before + ' real: ' + state.data.after + ' - ' + state.data.before + ' every: ' + state.data_update_every);
4376
4377                 state.globalSelectionSync(x);
4378
4379                 // fix legend zIndex using the internal structures of dygraph legend module
4380                 // this works, but it is a hack!
4381                 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4382             },
4383             unhighlightCallback: function(event) {
4384                 void(event);
4385
4386                 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4387                     state.log('dygraphUnhighlightCallback()');
4388
4389                 state.unpauseChart();
4390                 state.globalSelectionSyncStop();
4391             },
4392             interactionModel : {
4393                 mousedown: function(event, dygraph, context) {
4394                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4395                         state.log('interactionModel.mousedown()');
4396
4397                     state.dygraph_user_action = true;
4398                     state.globalSelectionSyncStop();
4399
4400                     if(NETDATA.options.debug.dygraph === true)
4401                         state.log('dygraphMouseDown()');
4402
4403                     // Right-click should not initiate a zoom.
4404                     if(event.button && event.button === 2) return;
4405
4406                     context.initializeMouseDown(event, dygraph, context);
4407
4408                     if(event.button && event.button === 1) {
4409                         if (event.altKey || event.shiftKey) {
4410                             state.setMode('pan');
4411                             state.globalSelectionSyncDelay();
4412                             Dygraph.startPan(event, dygraph, context);
4413                         }
4414                         else {
4415                             state.setMode('zoom');
4416                             state.globalSelectionSyncDelay();
4417                             Dygraph.startZoom(event, dygraph, context);
4418                         }
4419                     }
4420                     else {
4421                         if (event.altKey || event.shiftKey) {
4422                             state.setMode('zoom');
4423                             state.globalSelectionSyncDelay();
4424                             Dygraph.startZoom(event, dygraph, context);
4425                         }
4426                         else {
4427                             state.setMode('pan');
4428                             state.globalSelectionSyncDelay();
4429                             Dygraph.startPan(event, dygraph, context);
4430                         }
4431                     }
4432                 },
4433                 mousemove: function(event, dygraph, context) {
4434                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4435                         state.log('interactionModel.mousemove()');
4436
4437                     if(context.isPanning) {
4438                         state.dygraph_user_action = true;
4439                         state.globalSelectionSyncStop();
4440                         state.globalSelectionSyncDelay();
4441                         state.setMode('pan');
4442                         context.is2DPan = false;
4443                         Dygraph.movePan(event, dygraph, context);
4444                     }
4445                     else if(context.isZooming) {
4446                         state.dygraph_user_action = true;
4447                         state.globalSelectionSyncStop();
4448                         state.globalSelectionSyncDelay();
4449                         state.setMode('zoom');
4450                         Dygraph.moveZoom(event, dygraph, context);
4451                     }
4452                 },
4453                 mouseup: function(event, dygraph, context) {
4454                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4455                         state.log('interactionModel.mouseup()');
4456
4457                     if (context.isPanning) {
4458                         state.dygraph_user_action = true;
4459                         state.globalSelectionSyncDelay();
4460                         Dygraph.endPan(event, dygraph, context);
4461                     }
4462                     else if (context.isZooming) {
4463                         state.dygraph_user_action = true;
4464                         state.globalSelectionSyncDelay();
4465                         Dygraph.endZoom(event, dygraph, context);
4466                     }
4467                 },
4468                 click: function(event, dygraph, context) {
4469                     void(dygraph);
4470                     void(context);
4471
4472                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4473                         state.log('interactionModel.click()');
4474
4475                     event.preventDefault();
4476                 },
4477                 dblclick: function(event, dygraph, context) {
4478                     void(event);
4479                     void(dygraph);
4480                     void(context);
4481
4482                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4483                         state.log('interactionModel.dblclick()');
4484                     NETDATA.resetAllCharts(state);
4485                 },
4486                 wheel: function(event, dygraph, context) {
4487                     void(context);
4488
4489                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4490                         state.log('interactionModel.wheel()');
4491
4492                     // Take the offset of a mouse event on the dygraph canvas and
4493                     // convert it to a pair of percentages from the bottom left.
4494                     // (Not top left, bottom is where the lower value is.)
4495                     function offsetToPercentage(g, offsetX, offsetY) {
4496                         // This is calculating the pixel offset of the leftmost date.
4497                         var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4498                         var yar0 = g.yAxisRange(0);
4499
4500                         // This is calculating the pixel of the higest value. (Top pixel)
4501                         var yOffset = g.toDomCoords(null, yar0[1])[1];
4502
4503                         // x y w and h are relative to the corner of the drawing area,
4504                         // so that the upper corner of the drawing area is (0, 0).
4505                         var x = offsetX - xOffset;
4506                         var y = offsetY - yOffset;
4507
4508                         // This is computing the rightmost pixel, effectively defining the
4509                         // width.
4510                         var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4511
4512                         // This is computing the lowest pixel, effectively defining the height.
4513                         var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4514
4515                         // Percentage from the left.
4516                         var xPct = w === 0 ? 0 : (x / w);
4517                         // Percentage from the top.
4518                         var yPct = h === 0 ? 0 : (y / h);
4519
4520                         // The (1-) part below changes it from "% distance down from the top"
4521                         // to "% distance up from the bottom".
4522                         return [xPct, (1-yPct)];
4523                     }
4524
4525                     // Adjusts [x, y] toward each other by zoomInPercentage%
4526                     // Split it so the left/bottom axis gets xBias/yBias of that change and
4527                     // tight/top gets (1-xBias)/(1-yBias) of that change.
4528                     //
4529                     // If a bias is missing it splits it down the middle.
4530                     function zoomRange(g, zoomInPercentage, xBias, yBias) {
4531                         xBias = xBias || 0.5;
4532                         yBias = yBias || 0.5;
4533
4534                         function adjustAxis(axis, zoomInPercentage, bias) {
4535                             var delta = axis[1] - axis[0];
4536                             var increment = delta * zoomInPercentage;
4537                             var foo = [increment * bias, increment * (1-bias)];
4538
4539                             return [ axis[0] + foo[0], axis[1] - foo[1] ];
4540                         }
4541
4542                         var yAxes = g.yAxisRanges();
4543                         var newYAxes = [];
4544                         for (var i = 0; i < yAxes.length; i++) {
4545                             newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4546                         }
4547
4548                         return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4549                     }
4550
4551                     if(event.altKey || event.shiftKey) {
4552                         state.dygraph_user_action = true;
4553
4554                         state.globalSelectionSyncStop();
4555                         state.globalSelectionSyncDelay();
4556
4557                         // http://dygraphs.com/gallery/interaction-api.js
4558                         var normal_def;
4559                         if(typeof event.wheelDelta === 'number' && !isNaN(event.wheelDelta))
4560                             // chrome
4561                             normal_def = event.wheelDelta / 40;
4562                         else
4563                             // firefox
4564                             normal_def = event.deltaY * -1.2;
4565
4566                         var normal = (event.detail) ? event.detail * -1 : normal_def;
4567                         var percentage = normal / 50;
4568
4569                         if (!(event.offsetX && event.offsetY)){
4570                             event.offsetX = event.layerX - event.target.offsetLeft;
4571                             event.offsetY = event.layerY - event.target.offsetTop;
4572                         }
4573
4574                         var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4575                         var xPct = percentages[0];
4576                         var yPct = percentages[1];
4577
4578                         var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4579                         var after = new_x_range[0];
4580                         var before = new_x_range[1];
4581
4582                         var first = state.netdata_first + state.data_update_every;
4583                         var last = state.netdata_last + state.data_update_every;
4584
4585                         if(before > last) {
4586                             after -= (before - last);
4587                             before = last;
4588                         }
4589                         if(after < first) {
4590                             after = first;
4591                         }
4592
4593                         state.setMode('zoom');
4594                         if(state.updateChartPanOrZoom(after, before) === true)
4595                             dygraph.updateOptions({ dateWindow: [ after, before ] });
4596
4597                         event.preventDefault();
4598                     }
4599                 },
4600                 touchstart: function(event, dygraph, context) {
4601                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4602                         state.log('interactionModel.touchstart()');
4603
4604                     state.dygraph_user_action = true;
4605                     state.setMode('zoom');
4606                     state.pauseChart();
4607
4608                     Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4609
4610                     // we overwrite the touch directions at the end, to overwrite
4611                     // the internal default of dygraphs
4612                     context.touchDirections = { x: true, y: false };
4613
4614                     state.dygraph_last_touch_start = Date.now();
4615                     state.dygraph_last_touch_move = 0;
4616
4617                     if(typeof event.touches[0].pageX === 'number')
4618                         state.dygraph_last_touch_page_x = event.touches[0].pageX;
4619                     else
4620                         state.dygraph_last_touch_page_x = 0;
4621                 },
4622                 touchmove: function(event, dygraph, context) {
4623                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4624                         state.log('interactionModel.touchmove()');
4625
4626                     state.dygraph_user_action = true;
4627                     Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4628
4629                     state.dygraph_last_touch_move = Date.now();
4630                 },
4631                 touchend: function(event, dygraph, context) {
4632                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4633                         state.log('interactionModel.touchend()');
4634
4635                     state.dygraph_user_action = true;
4636                     Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4637
4638                     // if it didn't move, it is a selection
4639                     if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4640                         // internal api of dygraphs
4641                         var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4642                         var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4643                         if(NETDATA.dygraphSetSelection(state, t) === true)
4644                             state.globalSelectionSync(t);
4645                     }
4646
4647                     // if it was double tap within double click time, reset the charts
4648                     var now = Date.now();
4649                     if(typeof state.dygraph_last_touch_end !== 'undefined') {
4650                         if(state.dygraph_last_touch_move === 0) {
4651                             var dt = now - state.dygraph_last_touch_end;
4652                             if(dt <= NETDATA.options.current.double_click_speed)
4653                                 NETDATA.resetAllCharts(state);
4654                         }
4655                     }
4656
4657                     // remember the timestamp of the last touch end
4658                     state.dygraph_last_touch_end = now;
4659                 }
4660             }
4661         };
4662
4663         if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4664             state.dygraph_options.drawGrid = false;
4665             state.dygraph_options.drawAxis = false;
4666             state.dygraph_options.title = undefined;
4667             state.dygraph_options.ylabel = undefined;
4668             state.dygraph_options.yLabelWidth = 0;
4669             state.dygraph_options.labelsDivWidth = 120;
4670             state.dygraph_options.labelsDivStyles.width = '120px';
4671             state.dygraph_options.labelsSeparateLines = true;
4672             state.dygraph_options.rightGap = 0;
4673             state.dygraph_options.yRangePad = 1;
4674         }
4675
4676         if(smooth === true) {
4677             state.dygraph_smooth_eligible = true;
4678
4679             if(NETDATA.options.current.smooth_plot === true)
4680                 state.dygraph_options.plotter = smoothPlotter;
4681         }
4682         else state.dygraph_smooth_eligible = false;
4683
4684         state.dygraph_instance = new Dygraph(state.element_chart,
4685             data.result.data, state.dygraph_options);
4686
4687         state.dygraph_force_zoom = false;
4688         state.dygraph_user_action = false;
4689         state.dygraph_last_rendered = Date.now();
4690
4691         if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4692             state.__commonMin = self.data('common-min') || null;
4693             state.__commonMax = self.data('common-max') || null;
4694         }
4695         else {
4696             state.log('incompatible version of dygraphs detected');
4697             state.__commonMin = null;
4698             state.__commonMax = null;
4699         }
4700
4701         return true;
4702     };
4703
4704     // ----------------------------------------------------------------------------------------------------------------
4705     // morris
4706
4707     NETDATA.morrisInitialize = function(callback) {
4708         if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4709
4710             // morris requires raphael
4711             if(!NETDATA.chartLibraries.raphael.initialized) {
4712                 if(NETDATA.chartLibraries.raphael.enabled) {
4713                     NETDATA.raphaelInitialize(function() {
4714                         NETDATA.morrisInitialize(callback);
4715                     });
4716                 }
4717                 else {
4718                     NETDATA.chartLibraries.morris.enabled = false;
4719                     if(typeof callback === "function")
4720                         return callback();
4721                 }
4722             }
4723             else {
4724                 NETDATA._loadCSS(NETDATA.morris_css);
4725
4726                 $.ajax({
4727                     url: NETDATA.morris_js,
4728                     cache: true,
4729                     dataType: "script",
4730                     xhrFields: { withCredentials: true } // required for the cookie
4731                 })
4732                 .done(function() {
4733                     NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4734                 })
4735                 .fail(function() {
4736                     NETDATA.chartLibraries.morris.enabled = false;
4737                     NETDATA.error(100, NETDATA.morris_js);
4738                 })
4739                 .always(function() {
4740                     if(typeof callback === "function")
4741                         return callback();
4742                 });
4743             }
4744         }
4745         else {
4746             NETDATA.chartLibraries.morris.enabled = false;
4747             if(typeof callback === "function")
4748                 return callback();
4749         }
4750     };
4751
4752     NETDATA.morrisChartUpdate = function(state, data) {
4753         state.morris_instance.setData(data.result.data);
4754         return true;
4755     };
4756
4757     NETDATA.morrisChartCreate = function(state, data) {
4758
4759         state.morris_options = {
4760                 element: state.element_chart.id,
4761                 data: data.result.data,
4762                 xkey: 'time',
4763                 ykeys: data.dimension_names,
4764                 labels: data.dimension_names,
4765                 lineWidth: 2,
4766                 pointSize: 3,
4767                 smooth: true,
4768                 hideHover: 'auto',
4769                 parseTime: true,
4770                 continuousLine: false,
4771                 behaveLikeLine: false
4772         };
4773
4774         if(state.chart.chart_type === 'line')
4775             state.morris_instance = new Morris.Line(state.morris_options);
4776
4777         else if(state.chart.chart_type === 'area') {
4778             state.morris_options.behaveLikeLine = true;
4779             state.morris_instance = new Morris.Area(state.morris_options);
4780         }
4781         else // stacked
4782             state.morris_instance = new Morris.Area(state.morris_options);
4783
4784         return true;
4785     };
4786
4787     // ----------------------------------------------------------------------------------------------------------------
4788     // raphael
4789
4790     NETDATA.raphaelInitialize = function(callback) {
4791         if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4792             $.ajax({
4793                 url: NETDATA.raphael_js,
4794                 cache: true,
4795                 dataType: "script",
4796                 xhrFields: { withCredentials: true } // required for the cookie
4797             })
4798             .done(function() {
4799                 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4800             })
4801             .fail(function() {
4802                 NETDATA.chartLibraries.raphael.enabled = false;
4803                 NETDATA.error(100, NETDATA.raphael_js);
4804             })
4805             .always(function() {
4806                 if(typeof callback === "function")
4807                     return callback();
4808             });
4809         }
4810         else {
4811             NETDATA.chartLibraries.raphael.enabled = false;
4812             if(typeof callback === "function")
4813                 return callback();
4814         }
4815     };
4816
4817     NETDATA.raphaelChartUpdate = function(state, data) {
4818         $(state.element_chart).raphael(data.result, {
4819             width: state.chartWidth(),
4820             height: state.chartHeight()
4821         });
4822
4823         return false;
4824     };
4825
4826     NETDATA.raphaelChartCreate = function(state, data) {
4827         $(state.element_chart).raphael(data.result, {
4828             width: state.chartWidth(),
4829             height: state.chartHeight()
4830         });
4831
4832         return false;
4833     };
4834
4835     // ----------------------------------------------------------------------------------------------------------------
4836     // C3
4837
4838     NETDATA.c3Initialize = function(callback) {
4839         if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4840
4841             // C3 requires D3
4842             if(!NETDATA.chartLibraries.d3.initialized) {
4843                 if(NETDATA.chartLibraries.d3.enabled) {
4844                     NETDATA.d3Initialize(function() {
4845                         NETDATA.c3Initialize(callback);
4846                     });
4847                 }
4848                 else {
4849                     NETDATA.chartLibraries.c3.enabled = false;
4850                     if(typeof callback === "function")
4851                         return callback();
4852                 }
4853             }
4854             else {
4855                 NETDATA._loadCSS(NETDATA.c3_css);
4856
4857                 $.ajax({
4858                     url: NETDATA.c3_js,
4859                     cache: true,
4860                     dataType: "script",
4861                     xhrFields: { withCredentials: true } // required for the cookie
4862                 })
4863                 .done(function() {
4864                     NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4865                 })
4866                 .fail(function() {
4867                     NETDATA.chartLibraries.c3.enabled = false;
4868                     NETDATA.error(100, NETDATA.c3_js);
4869                 })
4870                 .always(function() {
4871                     if(typeof callback === "function")
4872                         return callback();
4873                 });
4874             }
4875         }
4876         else {
4877             NETDATA.chartLibraries.c3.enabled = false;
4878             if(typeof callback === "function")
4879                 return callback();
4880         }
4881     };
4882
4883     NETDATA.c3ChartUpdate = function(state, data) {
4884         state.c3_instance.destroy();
4885         return NETDATA.c3ChartCreate(state, data);
4886
4887         //state.c3_instance.load({
4888         //  rows: data.result,
4889         //  unload: true
4890         //});
4891
4892         //return true;
4893     };
4894
4895     NETDATA.c3ChartCreate = function(state, data) {
4896
4897         state.element_chart.id = 'c3-' + state.uuid;
4898         // console.log('id = ' + state.element_chart.id);
4899
4900         state.c3_instance = c3.generate({
4901             bindto: '#' + state.element_chart.id,
4902             size: {
4903                 width: state.chartWidth(),
4904                 height: state.chartHeight()
4905             },
4906             color: {
4907                 pattern: state.chartColors()
4908             },
4909             data: {
4910                 x: 'time',
4911                 rows: data.result,
4912                 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4913             },
4914             axis: {
4915                 x: {
4916                     type: 'timeseries',
4917                     tick: {
4918                         format: function(x) {
4919                             return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4920                         }
4921                     }
4922                 }
4923             },
4924             grid: {
4925                 x: {
4926                     show: true
4927                 },
4928                 y: {
4929                     show: true
4930                 }
4931             },
4932             point: {
4933                 show: false
4934             },
4935             line: {
4936                 connectNull: false
4937             },
4938             transition: {
4939                 duration: 0
4940             },
4941             interaction: {
4942                 enabled: true
4943             }
4944         });
4945
4946         // console.log(state.c3_instance);
4947
4948         return true;
4949     };
4950
4951     // ----------------------------------------------------------------------------------------------------------------
4952     // D3
4953
4954     NETDATA.d3Initialize = function(callback) {
4955         if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4956             $.ajax({
4957                 url: NETDATA.d3_js,
4958                 cache: true,
4959                 dataType: "script",
4960                 xhrFields: { withCredentials: true } // required for the cookie
4961             })
4962             .done(function() {
4963                 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4964             })
4965             .fail(function() {
4966                 NETDATA.chartLibraries.d3.enabled = false;
4967                 NETDATA.error(100, NETDATA.d3_js);
4968             })
4969             .always(function() {
4970                 if(typeof callback === "function")
4971                     return callback();
4972             });
4973         }
4974         else {
4975             NETDATA.chartLibraries.d3.enabled = false;
4976             if(typeof callback === "function")
4977                 return callback();
4978         }
4979     };
4980
4981     NETDATA.d3ChartUpdate = function(state, data) {
4982         return false;
4983     };
4984
4985     NETDATA.d3ChartCreate = function(state, data) {
4986         return false;
4987     };
4988
4989     // ----------------------------------------------------------------------------------------------------------------
4990     // google charts
4991
4992     NETDATA.googleInitialize = function(callback) {
4993         if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4994             $.ajax({
4995                 url: NETDATA.google_js,
4996                 cache: true,
4997                 dataType: "script",
4998                 xhrFields: { withCredentials: true } // required for the cookie
4999             })
5000             .done(function() {
5001                 NETDATA.registerChartLibrary('google', NETDATA.google_js);
5002                 google.load('visualization', '1.1', {
5003                     'packages': ['corechart', 'controls'],
5004                     'callback': callback
5005                 });
5006             })
5007             .fail(function() {
5008                 NETDATA.chartLibraries.google.enabled = false;
5009                 NETDATA.error(100, NETDATA.google_js);
5010                 if(typeof callback === "function")
5011                     return callback();
5012             });
5013         }
5014         else {
5015             NETDATA.chartLibraries.google.enabled = false;
5016             if(typeof callback === "function")
5017                 return callback();
5018         }
5019     };
5020
5021     NETDATA.googleChartUpdate = function(state, data) {
5022         var datatable = new google.visualization.DataTable(data.result);
5023         state.google_instance.draw(datatable, state.google_options);
5024         return true;
5025     };
5026
5027     NETDATA.googleChartCreate = function(state, data) {
5028         var datatable = new google.visualization.DataTable(data.result);
5029
5030         state.google_options = {
5031             colors: state.chartColors(),
5032
5033             // do not set width, height - the chart resizes itself
5034             //width: state.chartWidth(),
5035             //height: state.chartHeight(),
5036             lineWidth: 1,
5037             title: state.title,
5038             fontSize: 11,
5039             hAxis: {
5040             //  title: "Time of Day",
5041             //  format:'HH:mm:ss',
5042                 viewWindowMode: 'maximized',
5043                 slantedText: false,
5044                 format:'HH:mm:ss',
5045                 textStyle: {
5046                     fontSize: 9
5047                 },
5048                 gridlines: {
5049                     color: '#EEE'
5050                 }
5051             },
5052             vAxis: {
5053                 title: state.units,
5054                 viewWindowMode: 'pretty',
5055                 minValue: -0.1,
5056                 maxValue: 0.1,
5057                 direction: 1,
5058                 textStyle: {
5059                     fontSize: 9
5060                 },
5061                 gridlines: {
5062                     color: '#EEE'
5063                 }
5064             },
5065             chartArea: {
5066                 width: '65%',
5067                 height: '80%'
5068             },
5069             focusTarget: 'category',
5070             annotation: {
5071                 '1': {
5072                     style: 'line'
5073                 }
5074             },
5075             pointsVisible: 0,
5076             titlePosition: 'out',
5077             titleTextStyle: {
5078                 fontSize: 11
5079             },
5080             tooltip: {
5081                 isHtml: false,
5082                 ignoreBounds: true,
5083                 textStyle: {
5084                     fontSize: 9
5085                 }
5086             },
5087             curveType: 'function',
5088             areaOpacity: 0.3,
5089             isStacked: false
5090         };
5091
5092         switch(state.chart.chart_type) {
5093             case "area":
5094                 state.google_options.vAxis.viewWindowMode = 'maximized';
5095                 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5096                 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5097                 break;
5098
5099             case "stacked":
5100                 state.google_options.isStacked = true;
5101                 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5102                 state.google_options.vAxis.viewWindowMode = 'maximized';
5103                 state.google_options.vAxis.minValue = null;
5104                 state.google_options.vAxis.maxValue = null;
5105                 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5106                 break;
5107
5108             default:
5109             case "line":
5110                 state.google_options.lineWidth = 2;
5111                 state.google_instance = new google.visualization.LineChart(state.element_chart);
5112                 break;
5113         }
5114
5115         state.google_instance.draw(datatable, state.google_options);
5116         return true;
5117     };
5118
5119     // ----------------------------------------------------------------------------------------------------------------
5120
5121     NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5122         if(typeof value !== 'number') value = 0;
5123         if(typeof min !== 'number') min = 0;
5124         if(typeof max !== 'number') max = 0;
5125
5126         if(min > value) min = value;
5127         if(max < value) max = value;
5128
5129         // make sure it is zero based
5130         if(min > 0) min = 0;
5131         if(max < 0) max = 0;
5132
5133         var pcent = 0;
5134         if(value >= 0) {
5135             if(max !== 0)
5136                 pcent = Math.round(value * 100 / max);
5137             if(pcent === 0) pcent = 0.1;
5138         }
5139         else {
5140             if(min !== 0)
5141                 pcent = Math.round(-value * 100 / min);
5142             if(pcent === 0) pcent = -0.1;
5143         }
5144
5145         return pcent;
5146     };
5147
5148     // ----------------------------------------------------------------------------------------------------------------
5149     // easy-pie-chart
5150
5151     NETDATA.easypiechartInitialize = function(callback) {
5152         if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5153             $.ajax({
5154                 url: NETDATA.easypiechart_js,
5155                 cache: true,
5156                 dataType: "script",
5157                 xhrFields: { withCredentials: true } // required for the cookie
5158             })
5159                 .done(function() {
5160                     NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5161                 })
5162                 .fail(function() {
5163                     NETDATA.chartLibraries.easypiechart.enabled = false;
5164                     NETDATA.error(100, NETDATA.easypiechart_js);
5165                 })
5166                 .always(function() {
5167                     if(typeof callback === "function")
5168                         return callback();
5169                 })
5170         }
5171         else {
5172             NETDATA.chartLibraries.easypiechart.enabled = false;
5173             if(typeof callback === "function")
5174                 return callback();
5175         }
5176     };
5177
5178     NETDATA.easypiechartClearSelection = function(state) {
5179         if(typeof state.easyPieChartEvent !== 'undefined') {
5180             if(state.easyPieChartEvent.timer !== null)
5181                 clearTimeout(state.easyPieChartEvent.timer);
5182
5183             state.easyPieChartEvent.timer = null;
5184         }
5185
5186         if(state.isAutoRefreshable() === true && state.data !== null) {
5187             NETDATA.easypiechartChartUpdate(state, state.data);
5188         }
5189         else {
5190             state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5191             state.easyPieChart_instance.update(0);
5192         }
5193         state.easyPieChart_instance.enableAnimation();
5194
5195         return true;
5196     };
5197
5198     NETDATA.easypiechartSetSelection = function(state, t) {
5199         if(state.timeIsVisible(t) !== true)
5200             return NETDATA.easypiechartClearSelection(state);
5201
5202         var slot = state.calculateRowForTime(t);
5203         if(slot < 0 || slot >= state.data.result.length)
5204             return NETDATA.easypiechartClearSelection(state);
5205
5206         if(typeof state.easyPieChartEvent === 'undefined') {
5207             state.easyPieChartEvent = {
5208                 timer: null,
5209                 value: 0,
5210                 pcent: 0
5211             };
5212         }
5213
5214         var value = state.data.result[state.data.result.length - 1 - slot];
5215         var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5216         var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5217         var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5218
5219         state.easyPieChartEvent.value = value;
5220         state.easyPieChartEvent.pcent = pcent;
5221         state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5222
5223         if(state.easyPieChartEvent.timer === null) {
5224             state.easyPieChart_instance.disableAnimation();
5225
5226             state.easyPieChartEvent.timer = setTimeout(function() {
5227                 state.easyPieChartEvent.timer = null;
5228                 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5229             }, NETDATA.options.current.charts_selection_animation_delay);
5230         }
5231
5232         return true;
5233     };
5234
5235     NETDATA.easypiechartChartUpdate = function(state, data) {
5236         var value, min, max, pcent;
5237
5238         if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5239             value = null;
5240             pcent = 0;
5241         }
5242         else {
5243             value = data.result[0];
5244             min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5245             max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5246             pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5247         }
5248
5249         state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5250         state.easyPieChart_instance.update(pcent);
5251         return true;
5252     };
5253
5254     NETDATA.easypiechartChartCreate = function(state, data) {
5255         var self = $(state.element);
5256         var chart = $(state.element_chart);
5257
5258         var value = data.result[0];
5259         var min = self.data('easypiechart-min-value') || null;
5260         var max = self.data('easypiechart-max-value') || null;
5261         var adjust = self.data('easypiechart-adjust') || null;
5262
5263         if(min === null) {
5264             min = NETDATA.commonMin.get(state);
5265             state.easyPieChartMin = null;
5266         }
5267         else
5268             state.easyPieChartMin = min;
5269
5270         if(max === null) {
5271             max = NETDATA.commonMax.get(state);
5272             state.easyPieChartMax = null;
5273         }
5274         else
5275             state.easyPieChartMax = max;
5276
5277         var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5278
5279         chart.data('data-percent', pcent);
5280
5281         var size;
5282         switch(adjust) {
5283             case 'width': size = state.chartHeight(); break;
5284             case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5285             case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5286             case 'height':
5287             default: size = state.chartWidth(); break;
5288         }
5289         state.element.style.width = size + 'px';
5290         state.element.style.height = size + 'px';
5291
5292         var stroke = Math.floor(size / 22);
5293         if(stroke < 3) stroke = 2;
5294
5295         var valuefontsize = Math.floor((size * 2 / 3) / 5);
5296         var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5297         state.easyPieChartLabel = document.createElement('span');
5298         state.easyPieChartLabel.className = 'easyPieChartLabel';
5299         state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5300         state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5301         state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5302         state.element_chart.appendChild(state.easyPieChartLabel);
5303
5304         var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5305         var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5306         state.easyPieChartTitle = document.createElement('span');
5307         state.easyPieChartTitle.className = 'easyPieChartTitle';
5308         state.easyPieChartTitle.innerText = state.title;
5309         state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5310         state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5311         state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5312         state.element_chart.appendChild(state.easyPieChartTitle);
5313
5314         var unitfontsize = Math.round(titlefontsize * 0.9);
5315         var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5316         state.easyPieChartUnits = document.createElement('span');
5317         state.easyPieChartUnits.className = 'easyPieChartUnits';
5318         state.easyPieChartUnits.innerText = state.units;
5319         state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5320         state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5321         state.element_chart.appendChild(state.easyPieChartUnits);
5322
5323         var barColor = self.data('easypiechart-barcolor');
5324         if(typeof barColor === 'undefined' || barColor === null)
5325             barColor = state.chartColors()[0];
5326         else {
5327             // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5328             var tmp = eval(barColor);
5329             if(typeof tmp === 'function')
5330                 barColor = tmp;
5331         }
5332
5333         chart.easyPieChart({
5334             barColor: barColor,
5335             trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5336             scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5337             scaleLength: self.data('easypiechart-scalelength') || 5,
5338             lineCap: self.data('easypiechart-linecap') || 'round',
5339             lineWidth: self.data('easypiechart-linewidth') || stroke,
5340             trackWidth: self.data('easypiechart-trackwidth') || undefined,
5341             size: self.data('easypiechart-size') || size,
5342             rotate: self.data('easypiechart-rotate') || 0,
5343             animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5344             easing: self.data('easypiechart-easing') || undefined
5345         });
5346
5347         // when we just re-create the chart
5348         // do not animate the first update
5349         var animate = true;
5350         if(typeof state.easyPieChart_instance !== 'undefined')
5351             animate = false;
5352
5353         state.easyPieChart_instance = chart.data('easyPieChart');
5354         if(animate === false) state.easyPieChart_instance.disableAnimation();
5355         state.easyPieChart_instance.update(pcent);
5356         if(animate === false) state.easyPieChart_instance.enableAnimation();
5357         return true;
5358     };
5359
5360     // ----------------------------------------------------------------------------------------------------------------
5361     // gauge.js
5362
5363     NETDATA.gaugeInitialize = function(callback) {
5364         if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5365             $.ajax({
5366                 url: NETDATA.gauge_js,
5367                 cache: true,
5368                 dataType: "script",
5369                 xhrFields: { withCredentials: true } // required for the cookie
5370             })
5371                 .done(function() {
5372                     NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5373                 })
5374                 .fail(function() {
5375                     NETDATA.chartLibraries.gauge.enabled = false;
5376                     NETDATA.error(100, NETDATA.gauge_js);
5377                 })
5378                 .always(function() {
5379                     if(typeof callback === "function")
5380                         return callback();
5381                 })
5382         }
5383         else {
5384             NETDATA.chartLibraries.gauge.enabled = false;
5385             if(typeof callback === "function")
5386                 return callback();
5387         }
5388     };
5389
5390     NETDATA.gaugeAnimation = function(state, status) {
5391         var speed = 32;
5392
5393         if(typeof status === 'boolean' && status === false)
5394             speed = 1000000000;
5395         else if(typeof status === 'number')
5396             speed = status;
5397
5398         // console.log('gauge speed ' + speed);
5399         state.gauge_instance.animationSpeed = speed;
5400         state.___gaugeOld__.speed = speed;
5401     };
5402
5403     NETDATA.gaugeSet = function(state, value, min, max) {
5404         if(typeof value !== 'number') value = 0;
5405         if(typeof min !== 'number') min = 0;
5406         if(typeof max !== 'number') max = 0;
5407         if(value > max) max = value;
5408         if(value < min) min = value;
5409         if(min > max) {
5410             var t = min;
5411             min = max;
5412             max = t;
5413         }
5414         else if(min === max)
5415             max = min + 1;
5416
5417         // gauge.js has an issue if the needle
5418         // is smaller than min or larger than max
5419         // when we set the new values
5420         // the needle will go crazy
5421
5422         // to prevent it, we always feed it
5423         // with a percentage, so that the needle
5424         // is always between min and max
5425         var pcent = (value - min) * 100 / (max - min);
5426
5427         // these should never happen
5428         if(pcent < 0) pcent = 0;
5429         if(pcent > 100) pcent = 100;
5430
5431         state.gauge_instance.set(pcent);
5432         // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5433
5434         state.___gaugeOld__.value = value;
5435         state.___gaugeOld__.min = min;
5436         state.___gaugeOld__.max = max;
5437     };
5438
5439     NETDATA.gaugeSetLabels = function(state, value, min, max) {
5440         if(state.___gaugeOld__.valueLabel !== value) {
5441             state.___gaugeOld__.valueLabel = value;
5442             state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5443         }
5444         if(state.___gaugeOld__.minLabel !== min) {
5445             state.___gaugeOld__.minLabel = min;
5446             state.gaugeChartMin.innerText = state.legendFormatValue(min);
5447         }
5448         if(state.___gaugeOld__.maxLabel !== max) {
5449             state.___gaugeOld__.maxLabel = max;
5450             state.gaugeChartMax.innerText = state.legendFormatValue(max);
5451         }
5452     };
5453
5454     NETDATA.gaugeClearSelection = function(state) {
5455         if(typeof state.gaugeEvent !== 'undefined') {
5456             if(state.gaugeEvent.timer !== null)
5457                 clearTimeout(state.gaugeEvent.timer);
5458
5459             state.gaugeEvent.timer = null;
5460         }
5461
5462         if(state.isAutoRefreshable() === true && state.data !== null) {
5463             NETDATA.gaugeChartUpdate(state, state.data);
5464         }
5465         else {
5466             NETDATA.gaugeAnimation(state, false);
5467             NETDATA.gaugeSet(state, null, null, null);
5468             NETDATA.gaugeSetLabels(state, null, null, null);
5469         }
5470
5471         NETDATA.gaugeAnimation(state, true);
5472         return true;
5473     };
5474
5475     NETDATA.gaugeSetSelection = function(state, t) {
5476         if(state.timeIsVisible(t) !== true)
5477             return NETDATA.gaugeClearSelection(state);
5478
5479         var slot = state.calculateRowForTime(t);
5480         if(slot < 0 || slot >= state.data.result.length)
5481             return NETDATA.gaugeClearSelection(state);
5482
5483         if(typeof state.gaugeEvent === 'undefined') {
5484             state.gaugeEvent = {
5485                 timer: null,
5486                 value: 0,
5487                 min: 0,
5488                 max: 0
5489             };
5490         }
5491
5492         var value = state.data.result[state.data.result.length - 1 - slot];
5493         var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5494         var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5495
5496         // make sure it is zero based
5497         if(min > 0) min = 0;
5498         if(max < 0) max = 0;
5499
5500         state.gaugeEvent.value = value;
5501         state.gaugeEvent.min = min;
5502         state.gaugeEvent.max = max;
5503         NETDATA.gaugeSetLabels(state, value, min, max);
5504
5505         if(state.gaugeEvent.timer === null) {
5506             NETDATA.gaugeAnimation(state, false);
5507
5508             state.gaugeEvent.timer = setTimeout(function() {
5509                 state.gaugeEvent.timer = null;
5510                 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5511             }, NETDATA.options.current.charts_selection_animation_delay);
5512         }
5513
5514         return true;
5515     };
5516
5517     NETDATA.gaugeChartUpdate = function(state, data) {
5518         var value, min, max;
5519
5520         if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5521             value = 0;
5522             min = 0;
5523             max = 1;
5524             NETDATA.gaugeSetLabels(state, null, null, null);
5525         }
5526         else {
5527             value = data.result[0];
5528             min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5529             max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5530             if(value < min) min = value;
5531             if(value > max) max = value;
5532
5533             // make sure it is zero based
5534             if(min > 0) min = 0;
5535             if(max < 0) max = 0;
5536
5537             NETDATA.gaugeSetLabels(state, value, min, max);
5538         }
5539
5540         NETDATA.gaugeSet(state, value, min, max);
5541         return true;
5542     };
5543
5544     NETDATA.gaugeChartCreate = function(state, data) {
5545         var self = $(state.element);
5546         // var chart = $(state.element_chart);
5547
5548         var value = data.result[0];
5549         var min = self.data('gauge-min-value') || null;
5550         var max = self.data('gauge-max-value') || null;
5551         var adjust = self.data('gauge-adjust') || null;
5552         var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5553         var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5554         var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5555         var stopColor = self.data('gauge-stop-color') || void 0;
5556         var generateGradient = self.data('gauge-generate-gradient') || false;
5557
5558         if(min === null) {
5559             min = NETDATA.commonMin.get(state);
5560             state.gaugeMin = null;
5561         }
5562         else
5563             state.gaugeMin = min;
5564
5565         if(max === null) {
5566             max = NETDATA.commonMax.get(state);
5567             state.gaugeMax = null;
5568         }
5569         else
5570             state.gaugeMax = max;
5571
5572         // make sure it is zero based
5573         if(min > 0) min = 0;
5574         if(max < 0) max = 0;
5575
5576         var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5577         //switch(adjust) {
5578         //  case 'width': width = height * ratio; break;
5579         //  case 'height':
5580         //  default: height = width / ratio; break;
5581         //}
5582         //state.element.style.width = width.toString() + 'px';
5583         //state.element.style.height = height.toString() + 'px';
5584
5585         var lum_d = 0.05;
5586
5587         var options = {
5588             lines: 12,                  // The number of lines to draw
5589             angle: 0.15,                // The length of each line
5590             lineWidth: 0.44,            // 0.44 The line thickness
5591             pointer: {
5592                 length: 0.8,            // 0.9 The radius of the inner circle
5593                 strokeWidth: 0.035,     // The rotation offset
5594                 color: pointerColor     // Fill color
5595             },
5596             colorStart: startColor,     // Colors
5597             colorStop: stopColor,       // just experiment with them
5598             strokeColor: strokeColor,   // to see which ones work best for you
5599             limitMax: true,
5600             generateGradient: (generateGradient === true),
5601             gradientType: 0
5602         };
5603
5604         if (generateGradient.constructor === Array) {
5605             // example options:
5606             // data-gauge-generate-gradient="[0, 50, 100]"
5607             // data-gauge-gradient-percent-color-0="#FFFFFF"
5608             // data-gauge-gradient-percent-color-50="#999900"
5609             // data-gauge-gradient-percent-color-100="#000000"
5610
5611             options.percentColors = [];
5612             var len = generateGradient.length;
5613             while(len--) {
5614                 var pcent = generateGradient[len];
5615                 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5616                 if(color !== false) {
5617                     var a = [];
5618                     a[0] = pcent / 100;
5619                     a[1] = color;
5620                     options.percentColors.unshift(a);
5621                 }
5622             }
5623             if(options.percentColors.length === 0)
5624                 delete options.percentColors;
5625         }
5626         else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5627             options.percentColors = [
5628                 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5629                 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5630                 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5631                 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5632                 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5633                 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5634                 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5635                 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5636                 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5637                 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5638                 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5639         }
5640
5641         state.gauge_canvas = document.createElement('canvas');
5642         state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5643         state.gauge_canvas.className = 'gaugeChart';
5644         state.gauge_canvas.width  = width;
5645         state.gauge_canvas.height = height;
5646         state.element_chart.appendChild(state.gauge_canvas);
5647
5648         var valuefontsize = Math.floor(height / 6);
5649         var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5650         state.gaugeChartLabel = document.createElement('span');
5651         state.gaugeChartLabel.className = 'gaugeChartLabel';
5652         state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5653         state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5654         state.element_chart.appendChild(state.gaugeChartLabel);
5655
5656         var titlefontsize = Math.round(valuefontsize / 2);
5657         var titletop = 0;
5658         state.gaugeChartTitle = document.createElement('span');
5659         state.gaugeChartTitle.className = 'gaugeChartTitle';
5660         state.gaugeChartTitle.innerText = state.title;
5661         state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5662         state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5663         state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5664         state.element_chart.appendChild(state.gaugeChartTitle);
5665
5666         var unitfontsize = Math.round(titlefontsize * 0.9);
5667         state.gaugeChartUnits = document.createElement('span');
5668         state.gaugeChartUnits.className = 'gaugeChartUnits';
5669         state.gaugeChartUnits.innerText = state.units;
5670         state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5671         state.element_chart.appendChild(state.gaugeChartUnits);
5672
5673         state.gaugeChartMin = document.createElement('span');
5674         state.gaugeChartMin.className = 'gaugeChartMin';
5675         state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5676         state.element_chart.appendChild(state.gaugeChartMin);
5677
5678         state.gaugeChartMax = document.createElement('span');
5679         state.gaugeChartMax.className = 'gaugeChartMax';
5680         state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5681         state.element_chart.appendChild(state.gaugeChartMax);
5682
5683         // when we just re-create the chart
5684         // do not animate the first update
5685         var animate = true;
5686         if(typeof state.gauge_instance !== 'undefined')
5687             animate = false;
5688
5689         state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5690
5691         state.___gaugeOld__ = {
5692             value: value,
5693             min: min,
5694             max: max,
5695             valueLabel: null,
5696             minLabel: null,
5697             maxLabel: null
5698         };
5699
5700         // we will always feed a percentage
5701         state.gauge_instance.minValue = 0;
5702         state.gauge_instance.maxValue = 100;
5703
5704         NETDATA.gaugeAnimation(state, animate);
5705         NETDATA.gaugeSet(state, value, min, max);
5706         NETDATA.gaugeSetLabels(state, value, min, max);
5707         NETDATA.gaugeAnimation(state, true);
5708         return true;
5709     };
5710
5711     // ----------------------------------------------------------------------------------------------------------------
5712     // Charts Libraries Registration
5713
5714     NETDATA.chartLibraries = {
5715         "dygraph": {
5716             initialize: NETDATA.dygraphInitialize,
5717             create: NETDATA.dygraphChartCreate,
5718             update: NETDATA.dygraphChartUpdate,
5719             resize: function(state) {
5720                 if(typeof state.dygraph_instance.resize === 'function')
5721                     state.dygraph_instance.resize();
5722             },
5723             setSelection: NETDATA.dygraphSetSelection,
5724             clearSelection:  NETDATA.dygraphClearSelection,
5725             toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5726             initialized: false,
5727             enabled: true,
5728             format: function(state) { void(state); return 'json'; },
5729             options: function(state) { void(state); return 'ms|flip'; },
5730             legend: function(state) {
5731                 return (this.isSparkline(state) === false)?'right-side':null;
5732             },
5733             autoresize: function(state) { void(state); return true; },
5734             max_updates_to_recreate: function(state) { void(state); return 5000; },
5735             track_colors: function(state) { void(state); return true; },
5736             pixels_per_point: function(state) {
5737                 return (this.isSparkline(state) === false)?3:2;
5738             },
5739             isSparkline: function(state) {
5740                 if(typeof state.dygraph_sparkline === 'undefined') {
5741                     var t = $(state.element).data('dygraph-theme');
5742                     state.dygraph_sparkline = (t === 'sparkline');
5743                 }
5744                 return state.dygraph_sparkline;
5745             }
5746         },
5747         "sparkline": {
5748             initialize: NETDATA.sparklineInitialize,
5749             create: NETDATA.sparklineChartCreate,
5750             update: NETDATA.sparklineChartUpdate,
5751             resize: null,
5752             setSelection: undefined, // function(state, t) { void(state); return true; },
5753             clearSelection: undefined, // function(state) { void(state); return true; },
5754             toolboxPanAndZoom: null,
5755             initialized: false,
5756             enabled: true,
5757             format: function(state) { void(state); return 'array'; },
5758             options: function(state) { void(state); return 'flip|abs'; },
5759             legend: function(state) { void(state); return null; },
5760             autoresize: function(state) { void(state); return false; },
5761             max_updates_to_recreate: function(state) { void(state); return 5000; },
5762             track_colors: function(state) { void(state); return false; },
5763             pixels_per_point: function(state) { void(state); return 3; }
5764         },
5765         "peity": {
5766             initialize: NETDATA.peityInitialize,
5767             create: NETDATA.peityChartCreate,
5768             update: NETDATA.peityChartUpdate,
5769             resize: null,
5770             setSelection: undefined, // function(state, t) { void(state); return true; },
5771             clearSelection: undefined, // function(state) { void(state); return true; },
5772             toolboxPanAndZoom: null,
5773             initialized: false,
5774             enabled: true,
5775             format: function(state) { void(state); return 'ssvcomma'; },
5776             options: function(state) { void(state); return 'null2zero|flip|abs'; },
5777             legend: function(state) { void(state); return null; },
5778             autoresize: function(state) { void(state); return false; },
5779             max_updates_to_recreate: function(state) { void(state); return 5000; },
5780             track_colors: function(state) { void(state); return false; },
5781             pixels_per_point: function(state) { void(state); return 3; }
5782         },
5783         "morris": {
5784             initialize: NETDATA.morrisInitialize,
5785             create: NETDATA.morrisChartCreate,
5786             update: NETDATA.morrisChartUpdate,
5787             resize: null,
5788             setSelection: undefined, // function(state, t) { void(state); return true; },
5789             clearSelection: undefined, // function(state) { void(state); return true; },
5790             toolboxPanAndZoom: null,
5791             initialized: false,
5792             enabled: true,
5793             format: function(state) { void(state); return 'json'; },
5794             options: function(state) { void(state); return 'objectrows|ms'; },
5795             legend: function(state) { void(state); return null; },
5796             autoresize: function(state) { void(state); return false; },
5797             max_updates_to_recreate: function(state) { void(state); return 50; },
5798             track_colors: function(state) { void(state); return false; },
5799             pixels_per_point: function(state) { void(state); return 15; }
5800         },
5801         "google": {
5802             initialize: NETDATA.googleInitialize,
5803             create: NETDATA.googleChartCreate,
5804             update: NETDATA.googleChartUpdate,
5805             resize: null,
5806             setSelection: undefined, //function(state, t) { void(state); return true; },
5807             clearSelection: undefined, //function(state) { void(state); return true; },
5808             toolboxPanAndZoom: null,
5809             initialized: false,
5810             enabled: true,
5811             format: function(state) { void(state); return 'datatable'; },
5812             options: function(state) { void(state); return ''; },
5813             legend: function(state) { void(state); return null; },
5814             autoresize: function(state) { void(state); return false; },
5815             max_updates_to_recreate: function(state) { void(state); return 300; },
5816             track_colors: function(state) { void(state); return false; },
5817             pixels_per_point: function(state) { void(state); return 4; }
5818         },
5819         "raphael": {
5820             initialize: NETDATA.raphaelInitialize,
5821             create: NETDATA.raphaelChartCreate,
5822             update: NETDATA.raphaelChartUpdate,
5823             resize: null,
5824             setSelection: undefined, // function(state, t) { void(state); return true; },
5825             clearSelection: undefined, // function(state) { void(state); return true; },
5826             toolboxPanAndZoom: null,
5827             initialized: false,
5828             enabled: true,
5829             format: function(state) { void(state); return 'json'; },
5830             options: function(state) { void(state); return ''; },
5831             legend: function(state) { void(state); return null; },
5832             autoresize: function(state) { void(state); return false; },
5833             max_updates_to_recreate: function(state) { void(state); return 5000; },
5834             track_colors: function(state) { void(state); return false; },
5835             pixels_per_point: function(state) { void(state); return 3; }
5836         },
5837         "c3": {
5838             initialize: NETDATA.c3Initialize,
5839             create: NETDATA.c3ChartCreate,
5840             update: NETDATA.c3ChartUpdate,
5841             resize: null,
5842             setSelection: undefined, // function(state, t) { void(state); return true; },
5843             clearSelection: undefined, // function(state) { void(state); return true; },
5844             toolboxPanAndZoom: null,
5845             initialized: false,
5846             enabled: true,
5847             format: function(state) { void(state); return 'csvjsonarray'; },
5848             options: function(state) { void(state); return 'milliseconds'; },
5849             legend: function(state) { void(state); return null; },
5850             autoresize: function(state) { void(state); return false; },
5851             max_updates_to_recreate: function(state) { void(state); return 5000; },
5852             track_colors: function(state) { void(state); return false; },
5853             pixels_per_point: function(state) { void(state); return 15; }
5854         },
5855         "d3": {
5856             initialize: NETDATA.d3Initialize,
5857             create: NETDATA.d3ChartCreate,
5858             update: NETDATA.d3ChartUpdate,
5859             resize: null,
5860             setSelection: undefined, // function(state, t) { void(state); return true; },
5861             clearSelection: undefined, // function(state) { void(state); return true; },
5862             toolboxPanAndZoom: null,
5863             initialized: false,
5864             enabled: true,
5865             format: function(state) { void(state); return 'json'; },
5866             options: function(state) { void(state); return ''; },
5867             legend: function(state) { void(state); return null; },
5868             autoresize: function(state) { void(state); return false; },
5869             max_updates_to_recreate: function(state) { void(state); return 5000; },
5870             track_colors: function(state) { void(state); return false; },
5871             pixels_per_point: function(state) { void(state); return 3; }
5872         },
5873         "easypiechart": {
5874             initialize: NETDATA.easypiechartInitialize,
5875             create: NETDATA.easypiechartChartCreate,
5876             update: NETDATA.easypiechartChartUpdate,
5877             resize: null,
5878             setSelection: NETDATA.easypiechartSetSelection,
5879             clearSelection: NETDATA.easypiechartClearSelection,
5880             toolboxPanAndZoom: null,
5881             initialized: false,
5882             enabled: true,
5883             format: function(state) { void(state); return 'array'; },
5884             options: function(state) { void(state); return 'absolute'; },
5885             legend: function(state) { void(state); return null; },
5886             autoresize: function(state) { void(state); return false; },
5887             max_updates_to_recreate: function(state) { void(state); return 5000; },
5888             track_colors: function(state) { void(state); return true; },
5889             pixels_per_point: function(state) { void(state); return 3; },
5890             aspect_ratio: 100
5891         },
5892         "gauge": {
5893             initialize: NETDATA.gaugeInitialize,
5894             create: NETDATA.gaugeChartCreate,
5895             update: NETDATA.gaugeChartUpdate,
5896             resize: null,
5897             setSelection: NETDATA.gaugeSetSelection,
5898             clearSelection: NETDATA.gaugeClearSelection,
5899             toolboxPanAndZoom: null,
5900             initialized: false,
5901             enabled: true,
5902             format: function(state) { void(state); return 'array'; },
5903             options: function(state) { void(state); return 'absolute'; },
5904             legend: function(state) { void(state); return null; },
5905             autoresize: function(state) { void(state); return false; },
5906             max_updates_to_recreate: function(state) { void(state); return 5000; },
5907             track_colors: function(state) { void(state); return true; },
5908             pixels_per_point: function(state) { void(state); return 3; },
5909             aspect_ratio: 70
5910         }
5911     };
5912
5913     NETDATA.registerChartLibrary = function(library, url) {
5914         if(NETDATA.options.debug.libraries === true)
5915             console.log("registering chart library: " + library);
5916
5917         NETDATA.chartLibraries[library].url = url;
5918         NETDATA.chartLibraries[library].initialized = true;
5919         NETDATA.chartLibraries[library].enabled = true;
5920     };
5921
5922     // ----------------------------------------------------------------------------------------------------------------
5923     // Load required JS libraries and CSS
5924
5925     NETDATA.requiredJs = [
5926         {
5927             url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5928             async: false,
5929             isAlreadyLoaded: function() {
5930                 // check if bootstrap is loaded
5931                 if(typeof $().emulateTransitionEnd === 'function')
5932                     return true;
5933                 else {
5934                     return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap);
5935                 }
5936             }
5937         },
5938         {
5939             url: NETDATA.serverDefault + 'lib/perfect-scrollbar-0.6.15.min.js',
5940             isAlreadyLoaded: function() { return false; }
5941         }
5942     ];
5943
5944     NETDATA.requiredCSS = [
5945         {
5946             url: NETDATA.themes.current.bootstrap_css,
5947             isAlreadyLoaded: function() {
5948                 return (typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap);
5949             }
5950         },
5951         {
5952             url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
5953             isAlreadyLoaded: function() { return false; }
5954         },
5955         {
5956             url: NETDATA.themes.current.dashboard_css,
5957             isAlreadyLoaded: function() { return false; }
5958         }
5959     ];
5960
5961     NETDATA.loadedRequiredJs = 0;
5962     NETDATA.loadRequiredJs = function(index, callback) {
5963         if(index >= NETDATA.requiredJs.length) {
5964             if(typeof callback === 'function')
5965                 return callback();
5966             return;
5967         }
5968
5969         if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5970             NETDATA.loadedRequiredJs++;
5971             NETDATA.loadRequiredJs(++index, callback);
5972             return;
5973         }
5974
5975         if(NETDATA.options.debug.main_loop === true)
5976             console.log('loading ' + NETDATA.requiredJs[index].url);
5977
5978         var async = true;
5979         if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5980             async = false;
5981
5982         $.ajax({
5983             url: NETDATA.requiredJs[index].url,
5984             cache: true,
5985             dataType: "script",
5986             xhrFields: { withCredentials: true } // required for the cookie
5987         })
5988         .done(function() {
5989             if(NETDATA.options.debug.main_loop === true)
5990                 console.log('loaded ' + NETDATA.requiredJs[index].url);
5991         })
5992         .fail(function() {
5993             alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5994         })
5995         .always(function() {
5996             NETDATA.loadedRequiredJs++;
5997
5998             if(async === false)
5999                 NETDATA.loadRequiredJs(++index, callback);
6000         });
6001
6002         if(async === true)
6003             NETDATA.loadRequiredJs(++index, callback);
6004     };
6005
6006     NETDATA.loadRequiredCSS = function(index) {
6007         if(index >= NETDATA.requiredCSS.length)
6008             return;
6009
6010         if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
6011             NETDATA.loadRequiredCSS(++index);
6012             return;
6013         }
6014
6015         if(NETDATA.options.debug.main_loop === true)
6016             console.log('loading ' + NETDATA.requiredCSS[index].url);
6017
6018         NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
6019         NETDATA.loadRequiredCSS(++index);
6020     };
6021
6022
6023     // ----------------------------------------------------------------------------------------------------------------
6024     // Registry of netdata hosts
6025
6026     NETDATA.alarms = {
6027         onclick: null,                  // the callback to handle the click - it will be called with the alarm log entry
6028         chart_div_offset: 100,          // give that space above the chart when scrolling to it
6029         chart_div_id_prefix: 'chart_',  // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
6030         chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
6031
6032         ms_penalty: 0,                  // the time penalty of the next alarm
6033         ms_between_notifications: 500,  // firefox moves the alarms off-screen (above, outside the top of the screen)
6034                                         // if alarms are shown faster than: one per 500ms
6035
6036         notifications: false,           // when true, the browser supports notifications (may not be granted though)
6037         last_notification_id: 0,        // the id of the last alarm_log we have raised an alarm for
6038         first_notification_id: 0,       // the id of the first alarm_log entry for this session
6039                                         // this is used to prevent CLEAR notifications for past events
6040         // notifications_shown: [],
6041
6042         server: null,                   // the server to connect to for fetching alarms
6043         current: null,                  // the list of raised alarms - updated in the background
6044         callback: null,                 // a callback function to call every time the list of raised alarms is refreshed
6045
6046         notify: function(entry) {
6047             // console.log('alarm ' + entry.unique_id);
6048
6049             if(entry.updated === true) {
6050                 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6051                 return;
6052             }
6053
6054             var value_string = entry.value_string;
6055
6056             if(NETDATA.alarms.current !== null) {
6057                 // get the current value_string
6058                 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6059                 if(typeof t !== 'undefined' && entry.status === t.status)
6060                     value_string = t.value_string;
6061             }
6062
6063             var name = entry.name.replace(/_/g, ' ');
6064             var status = entry.status.toLowerCase();
6065             var title = name + ' = ' + value_string.toString();
6066             var tag = entry.alarm_id;
6067             var icon = 'images/seo-performance-128.png';
6068             var interaction = false;
6069             var data = entry;
6070             var show = true;
6071
6072             // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' +  entry.status);
6073
6074             switch(entry.status) {
6075                 case 'REMOVED':
6076                     show = false;
6077                     break;
6078
6079                 case 'UNDEFINED':
6080                     return;
6081
6082                 case 'UNINITIALIZED':
6083                     return;
6084
6085                 case 'CLEAR':
6086                     if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6087                         // console.log('alarm ' + entry.unique_id + ' is not current');
6088                         return;
6089                     }
6090                     if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6091                         // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6092                         return;
6093                     }
6094                     if(entry.no_clear_notification === true) {
6095                         // console.log('alarm' + entry.unique_id + ' is CLEAR but has no_clear_notification flag');
6096                         return;
6097                     }
6098                     title = name + ' back to normal (' + value_string.toString() + ')';
6099                     icon = 'images/check-mark-2-128-green.png'
6100                     interaction = false;
6101                     break;
6102
6103                 case 'WARNING':
6104                     if(entry.old_status === 'CRITICAL')
6105                         status = 'demoted to ' + entry.status.toLowerCase();
6106
6107                     icon = 'images/alert-128-orange.png';
6108                     interaction = false;
6109                     break;
6110
6111                 case 'CRITICAL':
6112                     if(entry.old_status === 'WARNING')
6113                         status = 'escalated to ' + entry.status.toLowerCase();
6114                     
6115                     icon = 'images/alert-128-red.png'
6116                     interaction = true;
6117                     break;
6118
6119                 default:
6120                     console.log('invalid alarm status ' + entry.status);
6121                     return;
6122             }
6123
6124             /*
6125             // cleanup old notifications with the same alarm_id as this one
6126             // FIXME: it does not seem to work on any web browser!
6127             var len = NETDATA.alarms.notifications_shown.length;
6128             while(len--) {
6129                 var n = NETDATA.alarms.notifications_shown[len];
6130                 if(n.data.alarm_id === entry.alarm_id) {
6131                     console.log('removing old alarm ' + n.data.unique_id);
6132
6133                     // close the notification
6134                     n.close.bind(n);
6135
6136                     // remove it from the array
6137                     NETDATA.alarms.notifications_shown.splice(len, 1);
6138                     len = NETDATA.alarms.notifications_shown.length;
6139                 }
6140             }
6141             */
6142
6143             if(show === true) {
6144
6145                 setTimeout(function() {
6146                     // show this notification
6147                     // console.log('new notification: ' + title);
6148                     var n = new Notification(title, {
6149                         body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6150                         tag: tag,
6151                         requireInteraction: interaction,
6152                         icon: NETDATA.serverDefault + icon,
6153                         data: data
6154                     });
6155
6156                     n.onclick = function(event) {
6157                         event.preventDefault();
6158                         NETDATA.alarms.onclick(event.target.data);
6159                     };
6160
6161                     // console.log(n);
6162                     // NETDATA.alarms.notifications_shown.push(n);
6163                     // console.log(entry);
6164                 }, NETDATA.alarms.ms_penalty);
6165
6166                 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6167             }
6168         },
6169
6170         scrollToChart: function(chart_id) {
6171             if(typeof chart_id === 'string') {
6172                 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6173                 if(typeof offset !== 'undefined') {
6174                     $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6175                     return true;
6176                 }
6177             }
6178             return false;
6179         },
6180
6181         scrollToAlarm: function(alarm) {
6182             if(typeof alarm === 'object') {
6183                 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6184
6185                 if(ret === true && NETDATA.options.page_is_visible === false)
6186                     window.focus();
6187                 //    alert('netdata dashboard will now scroll to chart: ' + alarm.chart + '\n\nThis alarm opened to bring the browser window in front of the screen. Click on the dashboard to prevent it from appearing again.');
6188             }
6189
6190         },
6191
6192         notifyAll: function() {
6193             // console.log('FETCHING ALARM LOG');
6194             NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6195                 // console.log('ALARM LOG FETCHED');
6196
6197                 if(data === null || typeof data !== 'object') {
6198                     console.log('invalid alarms log response');
6199                     return;
6200                 }
6201
6202                 if(data.length === 0) {
6203                     console.log('received empty alarm log');
6204                     return;
6205                 }
6206
6207                 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6208
6209                 data.sort(function(a, b) {
6210                     if(a.unique_id > b.unique_id) return -1;
6211                     if(a.unique_id < b.unique_id) return 1;
6212                     return 0;
6213                 });
6214
6215                 NETDATA.alarms.ms_penalty = 0;
6216
6217                 var len = data.length;
6218                 while(len--) {
6219                     if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6220                         NETDATA.alarms.notify(data[len]);
6221                     }
6222                     //else
6223                     //    console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6224                 }
6225
6226                 NETDATA.alarms.last_notification_id = data[0].unique_id;
6227                 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6228                 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6229             })
6230         },
6231
6232         check_notifications: function() {
6233             // returns true if we should fire 1+ notifications
6234
6235             if(NETDATA.alarms.notifications !== true) {
6236                 // console.log('notifications not available');
6237                 return false;
6238             }
6239
6240             if(Notification.permission !== 'granted') {
6241                 // console.log('notifications not granted');
6242                 return false;
6243             }
6244
6245             if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6246                 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6247
6248                 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6249                     // console.log('new alarms detected');
6250                     return true;
6251                 }
6252                 //else console.log('no new alarms');
6253             }
6254             // else console.log('cannot process alarms');
6255
6256             return false;
6257         },
6258
6259         get: function(what, callback) {
6260             $.ajax({
6261                 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6262                 async: true,
6263                 cache: false,
6264                 headers: {
6265                     'Cache-Control': 'no-cache, no-store',
6266                     'Pragma': 'no-cache'
6267                 },
6268                 xhrFields: { withCredentials: true } // required for the cookie
6269             })
6270                 .done(function(data) {
6271                     if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6272                         NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6273
6274                     if(typeof callback === 'function')
6275                         return callback(data);
6276                 })
6277                 .fail(function() {
6278                     NETDATA.error(415, NETDATA.alarms.server);
6279
6280                     if(typeof callback === 'function')
6281                         return callback(null);
6282                 });
6283         },
6284
6285         update_forever: function() {
6286             NETDATA.alarms.get('active', function(data) {
6287                 if(data !== null) {
6288                     NETDATA.alarms.current = data;
6289
6290                     if(NETDATA.alarms.check_notifications() === true) {
6291                         NETDATA.alarms.notifyAll();
6292                     }
6293
6294                     if (typeof NETDATA.alarms.callback === 'function') {
6295                         NETDATA.alarms.callback(data);
6296                     }
6297
6298                     // Health monitoring is disabled on this netdata
6299                     if(data.status === false) return;
6300                 }
6301
6302                 setTimeout(NETDATA.alarms.update_forever, 10000);
6303             });
6304         },
6305
6306         get_log: function(last_id, callback) {
6307             // console.log('fetching all log after ' + last_id.toString());
6308             $.ajax({
6309                 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6310                 async: true,
6311                 cache: false,
6312                 headers: {
6313                     'Cache-Control': 'no-cache, no-store',
6314                     'Pragma': 'no-cache'
6315                 },
6316                 xhrFields: { withCredentials: true } // required for the cookie
6317             })
6318                 .done(function(data) {
6319                     if(typeof callback === 'function')
6320                         return callback(data);
6321                 })
6322                 .fail(function() {
6323                     NETDATA.error(416, NETDATA.alarms.server);
6324
6325                     if(typeof callback === 'function')
6326                         return callback(null);
6327                 });
6328         },
6329
6330         init: function() {
6331             NETDATA.alarms.server = NETDATA.fixHost(NETDATA.serverDefault);
6332
6333             NETDATA.alarms.last_notification_id =
6334                 NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6335
6336             if(NETDATA.alarms.onclick === null)
6337                 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6338
6339             if(netdataShowAlarms === true) {
6340                 NETDATA.alarms.update_forever();
6341             
6342                 if('Notification' in window) {
6343                     // console.log('notifications available');
6344                     NETDATA.alarms.notifications = true;
6345
6346                     if(Notification.permission === 'default')
6347                         Notification.requestPermission();
6348                 }
6349             }
6350         }
6351     };
6352
6353     // ----------------------------------------------------------------------------------------------------------------
6354     // Registry of netdata hosts
6355
6356     NETDATA.registry = {
6357         server: null,       // the netdata registry server
6358         person_guid: null,  // the unique ID of this browser / user
6359         machine_guid: null, // the unique ID the netdata server that served dashboard.js
6360         hostname: null,     // the hostname of the netdata server that served dashboard.js
6361         machines: null,         // the user's other URLs
6362         machines_array: null,   // the user's other URLs in an array
6363         person_urls: null,
6364
6365         parsePersonUrls: function(person_urls) {
6366             // console.log(person_urls);
6367             NETDATA.registry.person_urls = person_urls;
6368
6369             if(person_urls) {
6370                 NETDATA.registry.machines = {};
6371                 NETDATA.registry.machines_array = [];
6372
6373                 var now = Date.now();
6374                 var apu = person_urls;
6375                 var i = apu.length;
6376                 while(i--) {
6377                     if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6378                         // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6379
6380                         var obj = {
6381                             guid: apu[i][0],
6382                             url: apu[i][1],
6383                             last_t: apu[i][2],
6384                             accesses: apu[i][3],
6385                             name: apu[i][4],
6386                             alternate_urls: []
6387                         };
6388                         obj.alternate_urls.push(apu[i][1]);
6389
6390                         NETDATA.registry.machines[apu[i][0]] = obj;
6391                         NETDATA.registry.machines_array.push(obj);
6392                     }
6393                     else {
6394                         // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6395
6396                         var pu = NETDATA.registry.machines[apu[i][0]];
6397                         if(pu.last_t < apu[i][2]) {
6398                             pu.url = apu[i][1];
6399                             pu.last_t = apu[i][2];
6400                             pu.name = apu[i][4];
6401                         }
6402                         pu.accesses += apu[i][3];
6403                         pu.alternate_urls.push(apu[i][1]);
6404                     }
6405                 }
6406             }
6407
6408             if(typeof netdataRegistryCallback === 'function')
6409                 netdataRegistryCallback(NETDATA.registry.machines_array);
6410         },
6411
6412         init: function() {
6413             if(netdataRegistry !== true) return;
6414
6415             NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6416                 if(data) {
6417                     NETDATA.registry.server = data.registry;
6418                     NETDATA.registry.machine_guid = data.machine_guid;
6419                     NETDATA.registry.hostname = data.hostname;
6420
6421                     NETDATA.registry.access(2, function (person_urls) {
6422                         NETDATA.registry.parsePersonUrls(person_urls);
6423
6424                     });
6425                 }
6426             });
6427         },
6428
6429         hello: function(host, callback) {
6430             host = NETDATA.fixHost(host);
6431
6432             // send HELLO to a netdata server:
6433             // 1. verifies the server is reachable
6434             // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6435             $.ajax({
6436                     url: host + '/api/v1/registry?action=hello',
6437                     async: true,
6438                     cache: false,
6439                     headers: {
6440                         'Cache-Control': 'no-cache, no-store',
6441                         'Pragma': 'no-cache'
6442                     },
6443                     xhrFields: { withCredentials: true } // required for the cookie
6444                 })
6445                 .done(function(data) {
6446                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6447                         NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6448                         data = null;
6449                     }
6450
6451                     if(typeof callback === 'function')
6452                         return callback(data);
6453                 })
6454                 .fail(function() {
6455                     NETDATA.error(407, host);
6456
6457                     if(typeof callback === 'function')
6458                         return callback(null);
6459                 });
6460         },
6461
6462         access: function(max_redirects, callback) {
6463             // send ACCESS to a netdata registry:
6464             // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6465             // 2. it responds with a list of netdata servers we know
6466             // the registry identifies us using a cookie it sets the first time we access it
6467             // the registry may respond with a redirect URL to send us to another registry
6468             $.ajax({
6469                     url: NETDATA.registry.server + '/api/v1/registry?action=access&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault), // + '&visible_url=' + encodeURIComponent(document.location),
6470                     async: true,
6471                     cache: false,
6472                     headers: {
6473                         'Cache-Control': 'no-cache, no-store',
6474                         'Pragma': 'no-cache'
6475                     },
6476                     xhrFields: { withCredentials: true } // required for the cookie
6477                 })
6478                 .done(function(data) {
6479                     var redirect = null;
6480                     if(typeof data.registry === 'string')
6481                         redirect = data.registry;
6482
6483                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6484                         NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6485                         data = null;
6486                     }
6487
6488                     if(data === null) {
6489                         if(redirect !== null && max_redirects > 0) {
6490                             NETDATA.registry.server = redirect;
6491                             NETDATA.registry.access(max_redirects - 1, callback);
6492                         }
6493                         else {
6494                             if(typeof callback === 'function')
6495                                 return callback(null);
6496                         }
6497                     }
6498                     else {
6499                         if(typeof data.person_guid === 'string')
6500                             NETDATA.registry.person_guid = data.person_guid;
6501
6502                         if(typeof callback === 'function')
6503                             return callback(data.urls);
6504                     }
6505                 })
6506                 .fail(function() {
6507                     NETDATA.error(410, NETDATA.registry.server);
6508
6509                     if(typeof callback === 'function')
6510                         return callback(null);
6511                 });
6512         },
6513
6514         delete: function(delete_url, callback) {
6515             // send DELETE to a netdata registry:
6516             $.ajax({
6517                 url: NETDATA.registry.server + '/api/v1/registry?action=delete&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault) + '&delete_url=' + encodeURIComponent(delete_url),
6518                 async: true,
6519                 cache: false,
6520                 headers: {
6521                     'Cache-Control': 'no-cache, no-store',
6522                     'Pragma': 'no-cache'
6523                 },
6524                 xhrFields: { withCredentials: true } // required for the cookie
6525             })
6526                 .done(function(data) {
6527                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6528                         NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6529                         data = null;
6530                     }
6531
6532                     if(typeof callback === 'function')
6533                         return callback(data);
6534                 })
6535                 .fail(function() {
6536                     NETDATA.error(412, NETDATA.registry.server);
6537
6538                     if(typeof callback === 'function')
6539                         return callback(null);
6540                 });
6541         },
6542
6543         search: function(machine_guid, callback) {
6544             // SEARCH for the URLs of a machine:
6545             $.ajax({
6546                 url: NETDATA.registry.server + '/api/v1/registry?action=search&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault) + '&for=' + machine_guid,
6547                 async: true,
6548                 cache: false,
6549                 headers: {
6550                     'Cache-Control': 'no-cache, no-store',
6551                     'Pragma': 'no-cache'
6552                 },
6553                 xhrFields: { withCredentials: true } // required for the cookie
6554             })
6555                 .done(function(data) {
6556                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6557                         NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6558                         data = null;
6559                     }
6560
6561                     if(typeof callback === 'function')
6562                         return callback(data);
6563                 })
6564                 .fail(function() {
6565                     NETDATA.error(418, NETDATA.registry.server);
6566
6567                     if(typeof callback === 'function')
6568                         return callback(null);
6569                 });
6570         },
6571
6572         switch: function(new_person_guid, callback) {
6573             // impersonate
6574             $.ajax({
6575                 url: NETDATA.registry.server + '/api/v1/registry?action=switch&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault) + '&to=' + new_person_guid,
6576                 async: true,
6577                 cache: false,
6578                 headers: {
6579                     'Cache-Control': 'no-cache, no-store',
6580                     'Pragma': 'no-cache'
6581                 },
6582                 xhrFields: { withCredentials: true } // required for the cookie
6583             })
6584                 .done(function(data) {
6585                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6586                         NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6587                         data = null;
6588                     }
6589
6590                     if(typeof callback === 'function')
6591                         return callback(data);
6592                 })
6593                 .fail(function() {
6594                     NETDATA.error(414, NETDATA.registry.server);
6595
6596                     if(typeof callback === 'function')
6597                         return callback(null);
6598                 });
6599         }
6600     };
6601
6602     // ----------------------------------------------------------------------------------------------------------------
6603     // Boot it!
6604
6605     if(typeof netdataPrepCallback === 'function')
6606         netdataPrepCallback();
6607
6608     NETDATA.errorReset();
6609     NETDATA.loadRequiredCSS(0);
6610
6611     NETDATA._loadjQuery(function() {
6612         NETDATA.loadRequiredJs(0, function() {
6613             if(typeof $().emulateTransitionEnd !== 'function') {
6614                 // bootstrap is not available
6615                 NETDATA.options.current.show_help = false;
6616             }
6617
6618             if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6619                 if(NETDATA.options.debug.main_loop === true)
6620                     console.log('starting chart refresh thread');
6621
6622                 NETDATA.start();
6623             }
6624         });
6625     });
6626 })(window, document);