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