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