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