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