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