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