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