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