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