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