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