]> arthur.barton.de Git - netdata.git/blob - web/dashboard.js
d2122ce8272f35636a131f29a67fa689f8471532
[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                 xhrFields: { withCredentials: true } // required for the cookie
2939             })
2940             .done(function(data) {
2941                 that.xhr = undefined;
2942
2943                 if(that.debug === true)
2944                     that.log('data received. updating chart.');
2945
2946                 that.updateChartWithData(data);
2947             })
2948             .fail(function(msg) {
2949                 that.xhr = undefined;
2950
2951                 if(msg.statusText !== 'abort')
2952                     error('data download failed for url: ' + that.data_url);
2953             })
2954             .always(function() {
2955                 that.xhr = undefined;
2956
2957                 NETDATA.statistics.refreshes_active--;
2958                 that._updating = false;
2959                 if(typeof callback === 'function') callback();
2960             });
2961
2962             return true;
2963         };
2964
2965         this.isVisible = function(nocache) {
2966             if(typeof nocache === 'undefined')
2967                 nocache = false;
2968
2969             // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2970
2971             // caching - we do not evaluate the charts visibility
2972             // if the page has not been scrolled since the last check
2973             if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2974                 return this.___isVisible___;
2975
2976             this.tm.last_visible_check = new Date().getTime();
2977
2978             var wh = window.innerHeight;
2979             var x = this.element.getBoundingClientRect();
2980             var ret = 0;
2981             var tolerance = 0;
2982
2983             if(x.width === 0 || x.height === 0) {
2984                 hideChart();
2985                 this.___isVisible___ = false;
2986                 return this.___isVisible___;
2987             }
2988
2989             if(x.top < 0 && -x.top > x.height) {
2990                 // the chart is entirely above
2991                 ret = -x.top - x.height;
2992             }
2993             else if(x.top > wh) {
2994                 // the chart is entirely below
2995                 ret = x.top - wh;
2996             }
2997
2998             if(ret > tolerance) {
2999                 // the chart is too far
3000
3001                 hideChart();
3002                 this.___isVisible___ = false;
3003                 return this.___isVisible___;
3004             }
3005             else {
3006                 // the chart is inside or very close
3007
3008                 unhideChart();
3009                 this.___isVisible___ = true;
3010                 return this.___isVisible___;
3011             }
3012         };
3013
3014         this.isAutoRefreshable = function() {
3015             return (this.current.autorefresh);
3016         };
3017
3018         this.canBeAutoRefreshed = function() {
3019             var now = new Date().getTime();
3020
3021             if(this.running === true) {
3022                 if(this.debug === true)
3023                     this.log('I am already running');
3024
3025                 return false;
3026             }
3027
3028             if(this.enabled === false) {
3029                 if(this.debug === true)
3030                     this.log('I am not enabled');
3031
3032                 return false;
3033             }
3034
3035             if(this.library === null || this.library.enabled === false) {
3036                 error('charting library "' + this.library_name + '" is not available');
3037                 if(this.debug === true)
3038                     this.log('My chart library ' + this.library_name + ' is not available');
3039
3040                 return false;
3041             }
3042
3043             if(this.isVisible() === false) {
3044                 if(NETDATA.options.debug.visibility === true || this.debug === true)
3045                     this.log('I am not visible');
3046
3047                 return false;
3048             }
3049
3050             if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3051                 if(this.debug === true)
3052                     this.log('timed force update detected - allowing this update');
3053
3054                 this.current.force_update_at = 0;
3055                 return true;
3056             }
3057
3058             if(this.isAutoRefreshable() === true) {
3059                 // allow the first update, even if the page is not visible
3060                 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3061                     if(NETDATA.options.debug.focus === true || this.debug === true)
3062                         this.log('canBeAutoRefreshed(): page does not have focus');
3063
3064                     return false;
3065                 }
3066
3067                 if(this.needsRecreation() === true) {
3068                     if(this.debug === true)
3069                         this.log('canBeAutoRefreshed(): needs re-creation.');
3070
3071                     return true;
3072                 }
3073
3074                 // options valid only for autoRefresh()
3075                 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3076                     if(NETDATA.globalPanAndZoom.isActive()) {
3077                         if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3078                             if(this.debug === true)
3079                                 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3080
3081                             return true;
3082                         }
3083                         else {
3084                             if(this.debug === true)
3085                                 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3086
3087                             return false;
3088                         }
3089                     }
3090
3091                     if(this.selected === true) {
3092                         if(this.debug === true)
3093                             this.log('canBeAutoRefreshed(): I have a selection in place.');
3094
3095                         return false;
3096                     }
3097
3098                     if(this.paused === true) {
3099                         if(this.debug === true)
3100                             this.log('canBeAutoRefreshed(): I am paused.');
3101
3102                         return false;
3103                     }
3104
3105                     if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3106                         if(this.debug === true)
3107                             this.log('canBeAutoRefreshed(): It is time to update me.');
3108
3109                         return true;
3110                     }
3111                 }
3112             }
3113
3114             return false;
3115         };
3116
3117         this.autoRefresh = function(callback) {
3118             if(this.canBeAutoRefreshed() === true && this.running === false) {
3119                 var state = this;
3120
3121                 state.running = true;
3122                 state.updateChart(function() {
3123                     state.running = false;
3124
3125                     if(typeof callback !== 'undefined')
3126                         callback();
3127                 });
3128             }
3129             else {
3130                 if(typeof callback !== 'undefined')
3131                     callback();
3132             }
3133         };
3134
3135         this._defaultsFromDownloadedChart = function(chart) {
3136             this.chart = chart;
3137             this.chart_url = chart.url;
3138             this.data_update_every = chart.update_every * 1000;
3139             this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3140             this.tm.last_info_downloaded = new Date().getTime();
3141
3142             if(this.title === null)
3143                 this.title = chart.title;
3144
3145             if(this.units === null)
3146                 this.units = chart.units;
3147         };
3148
3149         // fetch the chart description from the netdata server
3150         this.getChart = function(callback) {
3151             this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3152             if(this.chart) {
3153                 this._defaultsFromDownloadedChart(this.chart);
3154                 if(typeof callback === 'function') callback();
3155             }
3156             else {
3157                 this.chart_url = "/api/v1/chart?chart=" + this.id;
3158
3159                 if(this.debug === true)
3160                     this.log('downloading ' + this.chart_url);
3161
3162                 $.ajax( {
3163                     url:  this.host + this.chart_url,
3164                     cache: false,
3165                     async: true,
3166                     xhrFields: { withCredentials: true } // required for the cookie
3167                 })
3168                 .done(function(chart) {
3169                     chart.url = that.chart_url;
3170                     that._defaultsFromDownloadedChart(chart);
3171                     NETDATA.chartRegistry.add(that.host, that.id, chart);
3172                 })
3173                 .fail(function() {
3174                     NETDATA.error(404, that.chart_url);
3175                     error('chart not found on url "' + that.chart_url + '"');
3176                 })
3177                 .always(function() {
3178                     if(typeof callback === 'function') callback();
3179                 });
3180             }
3181         };
3182
3183         // ============================================================================================================
3184         // INITIALIZATION
3185
3186         init();
3187     };
3188
3189     NETDATA.resetAllCharts = function(state) {
3190         // first clear the global selection sync
3191         // to make sure no chart is in selected state
3192         state.globalSelectionSyncStop();
3193
3194         // there are 2 possibilities here
3195         // a. state is the global Pan and Zoom master
3196         // b. state is not the global Pan and Zoom master
3197         var master = true;
3198         if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3199             master = false;
3200
3201         // clear the global Pan and Zoom
3202         // this will also refresh the master
3203         // and unblock any charts currently mirroring the master
3204         NETDATA.globalPanAndZoom.clearMaster();
3205
3206         // if we were not the master, reset our status too
3207         // this is required because most probably the mouse
3208         // is over this chart, blocking it from auto-refreshing
3209         if(master === false && (state.paused === true || state.selected === true))
3210             state.resetChart();
3211     };
3212
3213     // get or create a chart state, given a DOM element
3214     NETDATA.chartState = function(element) {
3215         var state = $(element).data('netdata-state-object') || null;
3216         if(state === null) {
3217             state = new chartState(element);
3218             $(element).data('netdata-state-object', state);
3219         }
3220         return state;
3221     };
3222
3223     // ----------------------------------------------------------------------------------------------------------------
3224     // Library functions
3225
3226     // Load a script without jquery
3227     // This is used to load jquery - after it is loaded, we use jquery
3228     NETDATA._loadjQuery = function(callback) {
3229         if(typeof jQuery === 'undefined') {
3230             if(NETDATA.options.debug.main_loop === true)
3231                 console.log('loading ' + NETDATA.jQuery);
3232
3233             var script = document.createElement('script');
3234             script.type = 'text/javascript';
3235             script.async = true;
3236             script.src = NETDATA.jQuery;
3237
3238             // script.onabort = onError;
3239             script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3240             if(typeof callback === "function")
3241                 script.onload = callback;
3242
3243             var s = document.getElementsByTagName('script')[0];
3244             s.parentNode.insertBefore(script, s);
3245         }
3246         else if(typeof callback === "function")
3247             callback();
3248     };
3249
3250     NETDATA._loadCSS = function(filename) {
3251         // don't use jQuery here
3252         // styles are loaded before jQuery
3253         // to eliminate showing an unstyled page to the user
3254
3255         var fileref = document.createElement("link");
3256         fileref.setAttribute("rel", "stylesheet");
3257         fileref.setAttribute("type", "text/css");
3258         fileref.setAttribute("href", filename);
3259
3260         if (typeof fileref !== 'undefined')
3261             document.getElementsByTagName("head")[0].appendChild(fileref);
3262     };
3263
3264     NETDATA.colorHex2Rgb = function(hex) {
3265         // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3266         var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3267             hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3268             return r + r + g + g + b + b;
3269         });
3270
3271         var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3272         return result ? {
3273             r: parseInt(result[1], 16),
3274             g: parseInt(result[2], 16),
3275             b: parseInt(result[3], 16)
3276         } : null;
3277     };
3278
3279     NETDATA.colorLuminance = function(hex, lum) {
3280         // validate hex string
3281         hex = String(hex).replace(/[^0-9a-f]/gi, '');
3282         if (hex.length < 6)
3283             hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3284
3285         lum = lum || 0;
3286
3287         // convert to decimal and change luminosity
3288         var rgb = "#", c, i;
3289         for (i = 0; i < 3; i++) {
3290             c = parseInt(hex.substr(i*2,2), 16);
3291             c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3292             rgb += ("00"+c).substr(c.length);
3293         }
3294
3295         return rgb;
3296     };
3297
3298     NETDATA.guid = function() {
3299         function s4() {
3300             return Math.floor((1 + Math.random()) * 0x10000)
3301                     .toString(16)
3302                     .substring(1);
3303             }
3304
3305             return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3306     };
3307
3308     NETDATA.zeropad = function(x) {
3309         if(x > -10 && x < 10) return '0' + x.toString();
3310         else return x.toString();
3311     };
3312
3313     // user function to signal us the DOM has been
3314     // updated.
3315     NETDATA.updatedDom = function() {
3316         NETDATA.options.updated_dom = true;
3317     };
3318
3319     NETDATA.ready = function(callback) {
3320         NETDATA.options.pauseCallback = callback;
3321     };
3322
3323     NETDATA.pause = function(callback) {
3324         if(NETDATA.options.pause === true)
3325             callback();
3326         else
3327             NETDATA.options.pauseCallback = callback;
3328     };
3329
3330     NETDATA.unpause = function() {
3331         NETDATA.options.pauseCallback = null;
3332         NETDATA.options.updated_dom = true;
3333         NETDATA.options.pause = false;
3334     };
3335
3336     // ----------------------------------------------------------------------------------------------------------------
3337
3338     // this is purely sequencial charts refresher
3339     // it is meant to be autonomous
3340     NETDATA.chartRefresherNoParallel = function(index) {
3341         if(NETDATA.options.debug.mail_loop === true)
3342             console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3343
3344         if(NETDATA.options.updated_dom === true) {
3345             // the dom has been updated
3346             // get the dom parts again
3347             NETDATA.parseDom(NETDATA.chartRefresher);
3348             return;
3349         }
3350         if(index >= NETDATA.options.targets.length) {
3351             if(NETDATA.options.debug.main_loop === true)
3352                 console.log('waiting to restart main loop...');
3353
3354             NETDATA.options.auto_refresher_fast_weight = 0;
3355
3356             setTimeout(function() {
3357                 NETDATA.chartRefresher();
3358             }, NETDATA.options.current.idle_between_loops);
3359         }
3360         else {
3361             var state = NETDATA.options.targets[index];
3362
3363             if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3364                 if(NETDATA.options.debug.main_loop === true)
3365                     console.log('fast rendering...');
3366
3367                 state.autoRefresh(function() {
3368                     NETDATA.chartRefresherNoParallel(++index);
3369                 });
3370             }
3371             else {
3372                 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3373                 NETDATA.options.auto_refresher_fast_weight = 0;
3374
3375                 setTimeout(function() {
3376                     state.autoRefresh(function() {
3377                         NETDATA.chartRefresherNoParallel(++index);
3378                     });
3379                 }, NETDATA.options.current.idle_between_charts);
3380             }
3381         }
3382     };
3383
3384     // this is part of the parallel refresher
3385     // its cause is to refresh sequencially all the charts
3386     // that depend on chart library initialization
3387     // it will call the parallel refresher back
3388     // as soon as it sees a chart that its chart library
3389     // is initialized
3390     NETDATA.chartRefresher_uninitialized = function() {
3391         if(NETDATA.options.updated_dom === true) {
3392             // the dom has been updated
3393             // get the dom parts again
3394             NETDATA.parseDom(NETDATA.chartRefresher);
3395             return;
3396         }
3397
3398         if(NETDATA.options.sequencial.length === 0)
3399             NETDATA.chartRefresher();
3400         else {
3401             var state = NETDATA.options.sequencial.pop();
3402             if(state.library.initialized === true)
3403                 NETDATA.chartRefresher();
3404             else
3405                 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3406         }
3407     };
3408
3409     NETDATA.chartRefresherWaitTime = function() {
3410         return NETDATA.options.current.idle_parallel_loops;
3411     };
3412
3413     // the default refresher
3414     // it will create 2 sets of charts:
3415     // - the ones that can be refreshed in parallel
3416     // - the ones that depend on something else
3417     // the first set will be executed in parallel
3418     // the second will be given to NETDATA.chartRefresher_uninitialized()
3419     NETDATA.chartRefresher = function() {
3420         // console.log('auto-refresher...');
3421
3422         if(NETDATA.options.pause === true) {
3423             // console.log('auto-refresher is paused');
3424             setTimeout(NETDATA.chartRefresher,
3425                 NETDATA.chartRefresherWaitTime());
3426             return;
3427         }
3428
3429         if(typeof NETDATA.options.pauseCallback === 'function') {
3430             // console.log('auto-refresher is calling pauseCallback');
3431             NETDATA.options.pause = true;
3432             NETDATA.options.pauseCallback();
3433             NETDATA.chartRefresher();
3434             return;
3435         }
3436
3437         if(NETDATA.options.current.parallel_refresher === false) {
3438             // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3439             NETDATA.chartRefresherNoParallel(0);
3440             return;
3441         }
3442
3443         if(NETDATA.options.updated_dom === true) {
3444             // the dom has been updated
3445             // get the dom parts again
3446             // console.log('auto-refresher is calling parseDom()');
3447             NETDATA.parseDom(NETDATA.chartRefresher);
3448             return;
3449         }
3450
3451         var parallel = new Array();
3452         var targets = NETDATA.options.targets;
3453         var len = targets.length;
3454         var state;
3455         while(len--) {
3456             state = targets[len];
3457             if(state.isVisible() === false || state.running === true)
3458                 continue;
3459
3460             if(state.library.initialized === false) {
3461                 if(state.library.enabled === true) {
3462                     state.library.initialize(NETDATA.chartRefresher);
3463                     return;
3464                 }
3465                 else {
3466                     state.error('chart library "' + state.library_name + '" is not enabled.');
3467                 }
3468             }
3469
3470             parallel.unshift(state);
3471         }
3472
3473         if(parallel.length > 0) {
3474             // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3475             // this will execute the jobs in parallel
3476             $(parallel).each(function() {
3477                 this.autoRefresh();
3478             })
3479         }
3480         //else {
3481         //    console.log('auto-refresher nothing to do');
3482         //}
3483
3484         // run the next refresh iteration
3485         setTimeout(NETDATA.chartRefresher,
3486             NETDATA.chartRefresherWaitTime());
3487     };
3488
3489     NETDATA.parseDom = function(callback) {
3490         NETDATA.options.last_page_scroll = new Date().getTime();
3491         NETDATA.options.updated_dom = false;
3492
3493         var targets = $('div[data-netdata]'); //.filter(':visible');
3494
3495         if(NETDATA.options.debug.main_loop === true)
3496             console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3497
3498         NETDATA.options.targets = new Array();
3499         var len = targets.length;
3500         while(len--) {
3501             // the initialization will take care of sizing
3502             // and the "loading..." message
3503             NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3504         }
3505
3506         if(typeof callback === 'function') callback();
3507     };
3508
3509     // this is the main function - where everything starts
3510     NETDATA.start = function() {
3511         // this should be called only once
3512
3513         NETDATA.options.page_is_visible = true;
3514
3515         $(window).blur(function() {
3516             if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3517                 NETDATA.options.page_is_visible = false;
3518                 if(NETDATA.options.debug.focus === true)
3519                     console.log('Lost Focus!');
3520             }
3521         });
3522
3523         $(window).focus(function() {
3524             if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3525                 NETDATA.options.page_is_visible = true;
3526                 if(NETDATA.options.debug.focus === true)
3527                     console.log('Focus restored!');
3528             }
3529         });
3530
3531         if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3532             if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3533                 NETDATA.options.page_is_visible = false;
3534                 if(NETDATA.options.debug.focus === true)
3535                     console.log('Document has no focus!');
3536             }
3537         }
3538
3539         // bootstrap tab switching
3540         $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3541
3542         // bootstrap modal switching
3543         $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3544         $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3545
3546         // bootstrap collapse switching
3547         $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3548         $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3549
3550         NETDATA.parseDom(NETDATA.chartRefresher);
3551
3552         // Alarms initialization
3553         setTimeout(NETDATA.alarms.init, 1000);
3554
3555         // Registry initialization
3556         setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3557
3558         if(typeof netdataCallback === 'function')
3559             netdataCallback();
3560     };
3561
3562     // ----------------------------------------------------------------------------------------------------------------
3563     // peity
3564
3565     NETDATA.peityInitialize = function(callback) {
3566         if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3567             $.ajax({
3568                 url: NETDATA.peity_js,
3569                 cache: true,
3570                 dataType: "script",
3571                 xhrFields: { withCredentials: true } // required for the cookie
3572             })
3573             .done(function() {
3574                 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3575             })
3576             .fail(function() {
3577                 NETDATA.chartLibraries.peity.enabled = false;
3578                 NETDATA.error(100, NETDATA.peity_js);
3579             })
3580             .always(function() {
3581                 if(typeof callback === "function")
3582                     callback();
3583             });
3584         }
3585         else {
3586             NETDATA.chartLibraries.peity.enabled = false;
3587             if(typeof callback === "function")
3588                 callback();
3589         }
3590     };
3591
3592     NETDATA.peityChartUpdate = function(state, data) {
3593         state.peity_instance.innerHTML = data.result;
3594
3595         if(state.peity_options.stroke !== state.chartColors()[0]) {
3596             state.peity_options.stroke = state.chartColors()[0];
3597             if(state.chart.chart_type === 'line')
3598                 state.peity_options.fill = NETDATA.themes.current.background;
3599             else
3600                 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3601         }
3602
3603         $(state.peity_instance).peity('line', state.peity_options);
3604         return true;
3605     };
3606
3607     NETDATA.peityChartCreate = function(state, data) {
3608         state.peity_instance = document.createElement('div');
3609         state.element_chart.appendChild(state.peity_instance);
3610
3611         var self = $(state.element);
3612         state.peity_options = {
3613             stroke: NETDATA.themes.current.foreground,
3614             strokeWidth: self.data('peity-strokewidth') || 1,
3615             width: state.chartWidth(),
3616             height: state.chartHeight(),
3617             fill: NETDATA.themes.current.foreground
3618         };
3619
3620         NETDATA.peityChartUpdate(state, data);
3621         return true;
3622     };
3623
3624     // ----------------------------------------------------------------------------------------------------------------
3625     // sparkline
3626
3627     NETDATA.sparklineInitialize = function(callback) {
3628         if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3629             $.ajax({
3630                 url: NETDATA.sparkline_js,
3631                 cache: true,
3632                 dataType: "script",
3633                 xhrFields: { withCredentials: true } // required for the cookie
3634             })
3635             .done(function() {
3636                 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3637             })
3638             .fail(function() {
3639                 NETDATA.chartLibraries.sparkline.enabled = false;
3640                 NETDATA.error(100, NETDATA.sparkline_js);
3641             })
3642             .always(function() {
3643                 if(typeof callback === "function")
3644                     callback();
3645             });
3646         }
3647         else {
3648             NETDATA.chartLibraries.sparkline.enabled = false;
3649             if(typeof callback === "function")
3650                 callback();
3651         }
3652     };
3653
3654     NETDATA.sparklineChartUpdate = function(state, data) {
3655         state.sparkline_options.width = state.chartWidth();
3656         state.sparkline_options.height = state.chartHeight();
3657
3658         $(state.element_chart).sparkline(data.result, state.sparkline_options);
3659         return true;
3660     };
3661
3662     NETDATA.sparklineChartCreate = function(state, data) {
3663         var self = $(state.element);
3664         var type = self.data('sparkline-type') || 'line';
3665         var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3666         var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3667         var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3668         var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3669         var composite = self.data('sparkline-composite') || undefined;
3670         var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3671         var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3672         var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3673         var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3674         var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3675         var spotColor = self.data('sparkline-spotcolor') || undefined;
3676         var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3677         var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3678         var spotRadius = self.data('sparkline-spotradius') || undefined;
3679         var valueSpots = self.data('sparkline-valuespots') || undefined;
3680         var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3681         var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3682         var lineWidth = self.data('sparkline-linewidth') || undefined;
3683         var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3684         var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3685         var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3686         var xvalues = self.data('sparkline-xvalues') || undefined;
3687         var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3688         var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3689         var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3690         var disableInteraction = self.data('sparkline-disableinteraction') || false;
3691         var disableTooltips = self.data('sparkline-disabletooltips') || false;
3692         var disableHighlight = self.data('sparkline-disablehighlight') || false;
3693         var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3694         var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3695         var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3696         var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3697         var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3698         var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3699         var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3700         var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3701         var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3702         var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3703         var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3704         var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3705         var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3706         var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3707         var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3708         var animatedZooms = self.data('sparkline-animatedzooms') || false;
3709
3710         if(spotColor === 'disable') spotColor='';
3711         if(minSpotColor === 'disable') minSpotColor='';
3712         if(maxSpotColor === 'disable') maxSpotColor='';
3713
3714         state.sparkline_options = {
3715             type: type,
3716             lineColor: lineColor,
3717             fillColor: fillColor,
3718             chartRangeMin: chartRangeMin,
3719             chartRangeMax: chartRangeMax,
3720             composite: composite,
3721             enableTagOptions: enableTagOptions,
3722             tagOptionPrefix: tagOptionPrefix,
3723             tagValuesAttribute: tagValuesAttribute,
3724             disableHiddenCheck: disableHiddenCheck,
3725             defaultPixelsPerValue: defaultPixelsPerValue,
3726             spotColor: spotColor,
3727             minSpotColor: minSpotColor,
3728             maxSpotColor: maxSpotColor,
3729             spotRadius: spotRadius,
3730             valueSpots: valueSpots,
3731             highlightSpotColor: highlightSpotColor,
3732             highlightLineColor: highlightLineColor,
3733             lineWidth: lineWidth,
3734             normalRangeMin: normalRangeMin,
3735             normalRangeMax: normalRangeMax,
3736             drawNormalOnTop: drawNormalOnTop,
3737             xvalues: xvalues,
3738             chartRangeClip: chartRangeClip,
3739             chartRangeMinX: chartRangeMinX,
3740             chartRangeMaxX: chartRangeMaxX,
3741             disableInteraction: disableInteraction,
3742             disableTooltips: disableTooltips,
3743             disableHighlight: disableHighlight,
3744             highlightLighten: highlightLighten,
3745             highlightColor: highlightColor,
3746             tooltipContainer: tooltipContainer,
3747             tooltipClassname: tooltipClassname,
3748             tooltipChartTitle: state.title,
3749             tooltipFormat: tooltipFormat,
3750             tooltipPrefix: tooltipPrefix,
3751             tooltipSuffix: tooltipSuffix,
3752             tooltipSkipNull: tooltipSkipNull,
3753             tooltipValueLookups: tooltipValueLookups,
3754             tooltipFormatFieldlist: tooltipFormatFieldlist,
3755             tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3756             numberFormatter: numberFormatter,
3757             numberDigitGroupSep: numberDigitGroupSep,
3758             numberDecimalMark: numberDecimalMark,
3759             numberDigitGroupCount: numberDigitGroupCount,
3760             animatedZooms: animatedZooms,
3761             width: state.chartWidth(),
3762             height: state.chartHeight()
3763         };
3764
3765         $(state.element_chart).sparkline(data.result, state.sparkline_options);
3766         return true;
3767     };
3768
3769     // ----------------------------------------------------------------------------------------------------------------
3770     // dygraph
3771
3772     NETDATA.dygraph = {
3773         smooth: false
3774     };
3775
3776     NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3777         if(after < state.netdata_first)
3778             after = state.netdata_first;
3779
3780         if(before > state.netdata_last)
3781             before = state.netdata_last;
3782
3783         state.setMode('zoom');
3784         state.globalSelectionSyncStop();
3785         state.globalSelectionSyncDelay();
3786         state.dygraph_user_action = true;
3787         state.dygraph_force_zoom = true;
3788         state.updateChartPanOrZoom(after, before);
3789         NETDATA.globalPanAndZoom.setMaster(state, after, before);
3790     };
3791
3792     NETDATA.dygraphSetSelection = function(state, t) {
3793         if(typeof state.dygraph_instance !== 'undefined') {
3794             var r = state.calculateRowForTime(t);
3795             if(r !== -1)
3796                 state.dygraph_instance.setSelection(r);
3797             else {
3798                 state.dygraph_instance.clearSelection();
3799                 state.legendShowUndefined();
3800             }
3801         }
3802
3803         return true;
3804     };
3805
3806     NETDATA.dygraphClearSelection = function(state, t) {
3807         if(typeof state.dygraph_instance !== 'undefined') {
3808             state.dygraph_instance.clearSelection();
3809         }
3810         return true;
3811     };
3812
3813     NETDATA.dygraphSmoothInitialize = function(callback) {
3814         $.ajax({
3815             url: NETDATA.dygraph_smooth_js,
3816             cache: true,
3817             dataType: "script",
3818             xhrFields: { withCredentials: true } // required for the cookie
3819         })
3820         .done(function() {
3821             NETDATA.dygraph.smooth = true;
3822             smoothPlotter.smoothing = 0.3;
3823         })
3824         .fail(function() {
3825             NETDATA.dygraph.smooth = false;
3826         })
3827         .always(function() {
3828             if(typeof callback === "function")
3829                 callback();
3830         });
3831     };
3832
3833     NETDATA.dygraphInitialize = function(callback) {
3834         if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3835             $.ajax({
3836                 url: NETDATA.dygraph_js,
3837                 cache: true,
3838                 dataType: "script",
3839                 xhrFields: { withCredentials: true } // required for the cookie
3840             })
3841             .done(function() {
3842                 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3843             })
3844             .fail(function() {
3845                 NETDATA.chartLibraries.dygraph.enabled = false;
3846                 NETDATA.error(100, NETDATA.dygraph_js);
3847             })
3848             .always(function() {
3849                 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3850                     NETDATA.dygraphSmoothInitialize(callback);
3851                 else if(typeof callback === "function")
3852                     callback();
3853             });
3854         }
3855         else {
3856             NETDATA.chartLibraries.dygraph.enabled = false;
3857             if(typeof callback === "function")
3858                 callback();
3859         }
3860     };
3861
3862     NETDATA.dygraphChartUpdate = function(state, data) {
3863         var dygraph = state.dygraph_instance;
3864
3865         if(typeof dygraph === 'undefined')
3866             return NETDATA.dygraphChartCreate(state, data);
3867
3868         // when the chart is not visible, and hidden
3869         // if there is a window resize, dygraph detects
3870         // its element size as 0x0.
3871         // this will make it re-appear properly
3872
3873         if(state.tm.last_unhidden > state.dygraph_last_rendered)
3874             dygraph.resize();
3875
3876         var options = {
3877                 file: data.result.data,
3878                 colors: state.chartColors(),
3879                 labels: data.result.labels,
3880                 labelsDivWidth: state.chartWidth() - 70,
3881                 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3882         };
3883
3884         if(state.dygraph_force_zoom === true) {
3885             if(NETDATA.options.debug.dygraph === true || state.debug === true)
3886                 state.log('dygraphChartUpdate() forced zoom update');
3887
3888             options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3889             options.valueRange = state.dygraph_options.valueRange;
3890             options.isZoomedIgnoreProgrammaticZoom = true;
3891             state.dygraph_force_zoom = false;
3892         }
3893         else if(state.current.name !== 'auto') {
3894             if(NETDATA.options.debug.dygraph === true || state.debug === true)
3895                 state.log('dygraphChartUpdate() loose update');
3896
3897             options.valueRange = state.dygraph_options.valueRange;
3898         }
3899         else {
3900             if(NETDATA.options.debug.dygraph === true || state.debug === true)
3901                 state.log('dygraphChartUpdate() strict update');
3902
3903             options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3904             options.valueRange = state.dygraph_options.valueRange;
3905             options.isZoomedIgnoreProgrammaticZoom = true;
3906         }
3907
3908         if(state.dygraph_smooth_eligible === true) {
3909             if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3910                 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3911                 NETDATA.dygraphChartCreate(state, data);
3912                 return;
3913             }
3914         }
3915
3916         dygraph.updateOptions(options);
3917
3918         state.dygraph_last_rendered = new Date().getTime();
3919         return true;
3920     };
3921
3922     NETDATA.dygraphChartCreate = function(state, data) {
3923         if(NETDATA.options.debug.dygraph === true || state.debug === true)
3924             state.log('dygraphChartCreate()');
3925
3926         var self = $(state.element);
3927
3928         var chart_type = state.chart.chart_type;
3929         if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3930         chart_type = self.data('dygraph-type') || chart_type;
3931
3932         var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3933         smooth = self.data('dygraph-smooth') || smooth;
3934
3935         if(NETDATA.dygraph.smooth === false)
3936             smooth = false;
3937
3938         var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3939         var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3940
3941         state.dygraph_options = {
3942             colors: self.data('dygraph-colors') || state.chartColors(),
3943
3944             // leave a few pixels empty on the right of the chart
3945             rightGap: self.data('dygraph-rightgap') || 5,
3946             showRangeSelector: self.data('dygraph-showrangeselector') || false,
3947             showRoller: self.data('dygraph-showroller') || false,
3948
3949             title: self.data('dygraph-title') || state.title,
3950             titleHeight: self.data('dygraph-titleheight') || 19,
3951
3952             legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3953             labels: data.result.labels,
3954             labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3955             labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3956             labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3957             labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3958             labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3959             labelsKMB: false,
3960             labelsKMG2: false,
3961             showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3962             hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3963
3964             includeZero: self.data('dygraph-includezero') || ((chart_type === 'stacked')? true : false),
3965             xRangePad: self.data('dygraph-xrangepad') || 0,
3966             yRangePad: self.data('dygraph-yrangepad') || 1,
3967
3968             valueRange: self.data('dygraph-valuerange') || null,
3969
3970             ylabel: state.units,
3971             yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3972
3973             // the function to plot the chart
3974             plotter: null,
3975
3976             // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3977             strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3978             strokePattern: self.data('dygraph-strokepattern') || undefined,
3979
3980             // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3981             // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3982             drawPoints: self.data('dygraph-drawpoints') || false,
3983
3984             // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3985             drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3986
3987             connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3988             pointSize: self.data('dygraph-pointsize') || 1,
3989
3990             // enabling this makes the chart with little square lines
3991             stepPlot: self.data('dygraph-stepplot') || false,
3992
3993             // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3994             strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3995             strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3996
3997             fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3998             fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3999             stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
4000             stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
4001
4002             drawAxis: self.data('dygraph-drawaxis') || true,
4003             axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
4004             axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
4005             axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
4006
4007             drawGrid: self.data('dygraph-drawgrid') || true,
4008             gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4009             gridLineWidth: self.data('dygraph-gridlinewidth') || 0.4,
4010             gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4011
4012             maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4013             sigFigs: self.data('dygraph-sigfigs') || null,
4014             digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4015             valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4016
4017             highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4018             highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4019             highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4020
4021             pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4022             visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4023             axes: {
4024                 x: {
4025                     pixelsPerLabel: 50,
4026                     ticker: Dygraph.dateTicker,
4027                     axisLabelFormatter: function (d, gran) {
4028                         return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4029                     },
4030                     valueFormatter: function (ms) {
4031                         var d = new Date(ms);
4032                         return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4033                         // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4034                     }
4035                 },
4036                 y: {
4037                     pixelsPerLabel: 15,
4038                     valueFormatter: function (x) {
4039                         // we format legends with the state object
4040                         // no need to do anything here
4041                         // return (Math.round(x*100) / 100).toLocaleString();
4042                         // return state.legendFormatValue(x);
4043                         return x;
4044                     }
4045                 }
4046             },
4047             legendFormatter: function(data) {
4048                 var elements = state.element_legend_childs;
4049
4050                 // if the hidden div is not there
4051                 // we are not managing the legend
4052                 if(elements.hidden === null) return;
4053
4054                 if (typeof data.x !== 'undefined') {
4055                     state.legendSetDate(data.x);
4056                     var i = data.series.length;
4057                     while(i--) {
4058                         var series = data.series[i];
4059                         if(!series.isVisible) continue;
4060                         state.legendSetLabelValue(series.label, series.y);
4061                     }
4062                 }
4063
4064                 return '';
4065             },
4066             drawCallback: function(dygraph, is_initial) {
4067                 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4068                     state.dygraph_user_action = false;
4069
4070                     var x_range = dygraph.xAxisRange();
4071                     var after = Math.round(x_range[0]);
4072                     var before = Math.round(x_range[1]);
4073
4074                     if(NETDATA.options.debug.dygraph === true)
4075                         state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4076
4077                     if(before <= state.netdata_last && after >= state.netdata_first)
4078                         state.updateChartPanOrZoom(after, before);
4079                 }
4080             },
4081             zoomCallback: function(minDate, maxDate, yRanges) {
4082                 if(NETDATA.options.debug.dygraph === true)
4083                     state.log('dygraphZoomCallback()');
4084
4085                 state.globalSelectionSyncStop();
4086                 state.globalSelectionSyncDelay();
4087                 state.setMode('zoom');
4088
4089                 // refresh it to the greatest possible zoom level
4090                 state.dygraph_user_action = true;
4091                 state.dygraph_force_zoom = true;
4092                 state.updateChartPanOrZoom(minDate, maxDate);
4093             },
4094             highlightCallback: function(event, x, points, row, seriesName) {
4095                 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4096                     state.log('dygraphHighlightCallback()');
4097
4098                 state.pauseChart();
4099
4100                 // there is a bug in dygraph when the chart is zoomed enough
4101                 // the time it thinks is selected is wrong
4102                 // here we calculate the time t based on the row number selected
4103                 // which is ok
4104                 var t = state.data_after + row * state.data_update_every;
4105                 // 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);
4106
4107                 state.globalSelectionSync(x);
4108
4109                 // fix legend zIndex using the internal structures of dygraph legend module
4110                 // this works, but it is a hack!
4111                 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4112             },
4113             unhighlightCallback: function(event) {
4114                 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4115                     state.log('dygraphUnhighlightCallback()');
4116
4117                 state.unpauseChart();
4118                 state.globalSelectionSyncStop();
4119             },
4120             interactionModel : {
4121                 mousedown: function(event, dygraph, context) {
4122                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4123                         state.log('interactionModel.mousedown()');
4124
4125                     state.dygraph_user_action = true;
4126                     state.globalSelectionSyncStop();
4127
4128                     if(NETDATA.options.debug.dygraph === true)
4129                         state.log('dygraphMouseDown()');
4130
4131                     // Right-click should not initiate a zoom.
4132                     if(event.button && event.button === 2) return;
4133
4134                     context.initializeMouseDown(event, dygraph, context);
4135
4136                     if(event.button && event.button === 1) {
4137                         if (event.altKey || event.shiftKey) {
4138                             state.setMode('pan');
4139                             state.globalSelectionSyncDelay();
4140                             Dygraph.startPan(event, dygraph, context);
4141                         }
4142                         else {
4143                             state.setMode('zoom');
4144                             state.globalSelectionSyncDelay();
4145                             Dygraph.startZoom(event, dygraph, context);
4146                         }
4147                     }
4148                     else {
4149                         if (event.altKey || event.shiftKey) {
4150                             state.setMode('zoom');
4151                             state.globalSelectionSyncDelay();
4152                             Dygraph.startZoom(event, dygraph, context);
4153                         }
4154                         else {
4155                             state.setMode('pan');
4156                             state.globalSelectionSyncDelay();
4157                             Dygraph.startPan(event, dygraph, context);
4158                         }
4159                     }
4160                 },
4161                 mousemove: function(event, dygraph, context) {
4162                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4163                         state.log('interactionModel.mousemove()');
4164
4165                     if(context.isPanning) {
4166                         state.dygraph_user_action = true;
4167                         state.globalSelectionSyncStop();
4168                         state.globalSelectionSyncDelay();
4169                         state.setMode('pan');
4170                         Dygraph.movePan(event, dygraph, context);
4171                     }
4172                     else if(context.isZooming) {
4173                         state.dygraph_user_action = true;
4174                         state.globalSelectionSyncStop();
4175                         state.globalSelectionSyncDelay();
4176                         state.setMode('zoom');
4177                         Dygraph.moveZoom(event, dygraph, context);
4178                     }
4179                 },
4180                 mouseup: function(event, dygraph, context) {
4181                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4182                         state.log('interactionModel.mouseup()');
4183
4184                     if (context.isPanning) {
4185                         state.dygraph_user_action = true;
4186                         state.globalSelectionSyncDelay();
4187                         Dygraph.endPan(event, dygraph, context);
4188                     }
4189                     else if (context.isZooming) {
4190                         state.dygraph_user_action = true;
4191                         state.globalSelectionSyncDelay();
4192                         Dygraph.endZoom(event, dygraph, context);
4193                     }
4194                 },
4195                 click: function(event, dygraph, context) {
4196                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4197                         state.log('interactionModel.click()');
4198
4199                     event.preventDefault();
4200                 },
4201                 dblclick: function(event, dygraph, context) {
4202                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4203                         state.log('interactionModel.dblclick()');
4204                     NETDATA.resetAllCharts(state);
4205                 },
4206                 mousewheel: function(event, dygraph, context) {
4207                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4208                         state.log('interactionModel.mousewheel()');
4209
4210                     // Take the offset of a mouse event on the dygraph canvas and
4211                     // convert it to a pair of percentages from the bottom left.
4212                     // (Not top left, bottom is where the lower value is.)
4213                     function offsetToPercentage(g, offsetX, offsetY) {
4214                         // This is calculating the pixel offset of the leftmost date.
4215                         var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4216                         var yar0 = g.yAxisRange(0);
4217
4218                         // This is calculating the pixel of the higest value. (Top pixel)
4219                         var yOffset = g.toDomCoords(null, yar0[1])[1];
4220
4221                         // x y w and h are relative to the corner of the drawing area,
4222                         // so that the upper corner of the drawing area is (0, 0).
4223                         var x = offsetX - xOffset;
4224                         var y = offsetY - yOffset;
4225
4226                         // This is computing the rightmost pixel, effectively defining the
4227                         // width.
4228                         var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4229
4230                         // This is computing the lowest pixel, effectively defining the height.
4231                         var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4232
4233                         // Percentage from the left.
4234                         var xPct = w === 0 ? 0 : (x / w);
4235                         // Percentage from the top.
4236                         var yPct = h === 0 ? 0 : (y / h);
4237
4238                         // The (1-) part below changes it from "% distance down from the top"
4239                         // to "% distance up from the bottom".
4240                         return [xPct, (1-yPct)];
4241                     }
4242
4243                     // Adjusts [x, y] toward each other by zoomInPercentage%
4244                     // Split it so the left/bottom axis gets xBias/yBias of that change and
4245                     // tight/top gets (1-xBias)/(1-yBias) of that change.
4246                     //
4247                     // If a bias is missing it splits it down the middle.
4248                     function zoomRange(g, zoomInPercentage, xBias, yBias) {
4249                         xBias = xBias || 0.5;
4250                         yBias = yBias || 0.5;
4251
4252                         function adjustAxis(axis, zoomInPercentage, bias) {
4253                             var delta = axis[1] - axis[0];
4254                             var increment = delta * zoomInPercentage;
4255                             var foo = [increment * bias, increment * (1-bias)];
4256
4257                             return [ axis[0] + foo[0], axis[1] - foo[1] ];
4258                         }
4259
4260                         var yAxes = g.yAxisRanges();
4261                         var newYAxes = [];
4262                         for (var i = 0; i < yAxes.length; i++) {
4263                             newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4264                         }
4265
4266                         return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4267                     }
4268
4269                     if(event.altKey || event.shiftKey) {
4270                         state.dygraph_user_action = true;
4271
4272                         state.globalSelectionSyncStop();
4273                         state.globalSelectionSyncDelay();
4274
4275                         // http://dygraphs.com/gallery/interaction-api.js
4276                         var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4277                         var percentage = normal / 50;
4278
4279                         if (!(event.offsetX && event.offsetY)){
4280                             event.offsetX = event.layerX - event.target.offsetLeft;
4281                             event.offsetY = event.layerY - event.target.offsetTop;
4282                         }
4283
4284                         var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4285                         var xPct = percentages[0];
4286                         var yPct = percentages[1];
4287
4288                         var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4289
4290                         var after = new_x_range[0];
4291                         var before = new_x_range[1];
4292
4293                         var first = state.netdata_first + state.data_update_every;
4294                         var last = state.netdata_last + state.data_update_every;
4295
4296                         if(before > last) {
4297                             after -= (before - last);
4298                             before = last;
4299                         }
4300                         if(after < first) {
4301                             after = first;
4302                         }
4303
4304                         state.setMode('zoom');
4305                         if(state.updateChartPanOrZoom(after, before) === true)
4306                             dygraph.updateOptions({ dateWindow: [ after, before ] });
4307
4308                         event.preventDefault();
4309                     }
4310                 },
4311                 touchstart: function(event, dygraph, context) {
4312                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4313                         state.log('interactionModel.touchstart()');
4314
4315                     state.dygraph_user_action = true;
4316                     state.setMode('zoom');
4317                     state.pauseChart();
4318
4319                     Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4320
4321                     // we overwrite the touch directions at the end, to overwrite
4322                     // the internal default of dygraphs
4323                     context.touchDirections = { x: true, y: false };
4324
4325                     state.dygraph_last_touch_start = new Date().getTime();
4326                     state.dygraph_last_touch_move = 0;
4327
4328                     if(typeof event.touches[0].pageX === 'number')
4329                         state.dygraph_last_touch_page_x = event.touches[0].pageX;
4330                     else
4331                         state.dygraph_last_touch_page_x = 0;
4332                 },
4333                 touchmove: function(event, dygraph, context) {
4334                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4335                         state.log('interactionModel.touchmove()');
4336
4337                     state.dygraph_user_action = true;
4338                     Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4339
4340                     state.dygraph_last_touch_move = new Date().getTime();
4341                 },
4342                 touchend: function(event, dygraph, context) {
4343                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4344                         state.log('interactionModel.touchend()');
4345
4346                     state.dygraph_user_action = true;
4347                     Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4348
4349                     // if it didn't move, it is a selection
4350                     if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4351                         // internal api of dygraphs
4352                         var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4353                         var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4354                         if(NETDATA.dygraphSetSelection(state, t) === true)
4355                             state.globalSelectionSync(t);
4356                     }
4357
4358                     // if it was double tap within double click time, reset the charts
4359                     var now = new Date().getTime();
4360                     if(typeof state.dygraph_last_touch_end !== 'undefined') {
4361                         if(state.dygraph_last_touch_move === 0) {
4362                             var dt = now - state.dygraph_last_touch_end;
4363                             if(dt <= NETDATA.options.current.double_click_speed)
4364                                 NETDATA.resetAllCharts(state);
4365                         }
4366                     }
4367
4368                     // remember the timestamp of the last touch end
4369                     state.dygraph_last_touch_end = now;
4370                 }
4371             }
4372         };
4373
4374         if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4375             state.dygraph_options.drawGrid = false;
4376             state.dygraph_options.drawAxis = false;
4377             state.dygraph_options.title = undefined;
4378             state.dygraph_options.ylabel = undefined;
4379             state.dygraph_options.yLabelWidth = 0;
4380             state.dygraph_options.labelsDivWidth = 120;
4381             state.dygraph_options.labelsDivStyles.width = '120px';
4382             state.dygraph_options.labelsSeparateLines = true;
4383             state.dygraph_options.rightGap = 0;
4384             state.dygraph_options.yRangePad = 1;
4385         }
4386
4387         if(smooth === true) {
4388             state.dygraph_smooth_eligible = true;
4389
4390             if(NETDATA.options.current.smooth_plot === true)
4391                 state.dygraph_options.plotter = smoothPlotter;
4392         }
4393         else state.dygraph_smooth_eligible = false;
4394
4395         state.dygraph_instance = new Dygraph(state.element_chart,
4396             data.result.data, state.dygraph_options);
4397
4398         state.dygraph_force_zoom = false;
4399         state.dygraph_user_action = false;
4400         state.dygraph_last_rendered = new Date().getTime();
4401         return true;
4402     };
4403
4404     // ----------------------------------------------------------------------------------------------------------------
4405     // morris
4406
4407     NETDATA.morrisInitialize = function(callback) {
4408         if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4409
4410             // morris requires raphael
4411             if(!NETDATA.chartLibraries.raphael.initialized) {
4412                 if(NETDATA.chartLibraries.raphael.enabled) {
4413                     NETDATA.raphaelInitialize(function() {
4414                         NETDATA.morrisInitialize(callback);
4415                     });
4416                 }
4417                 else {
4418                     NETDATA.chartLibraries.morris.enabled = false;
4419                     if(typeof callback === "function")
4420                         callback();
4421                 }
4422             }
4423             else {
4424                 NETDATA._loadCSS(NETDATA.morris_css);
4425
4426                 $.ajax({
4427                     url: NETDATA.morris_js,
4428                     cache: true,
4429                     dataType: "script",
4430                     xhrFields: { withCredentials: true } // required for the cookie
4431                 })
4432                 .done(function() {
4433                     NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4434                 })
4435                 .fail(function() {
4436                     NETDATA.chartLibraries.morris.enabled = false;
4437                     NETDATA.error(100, NETDATA.morris_js);
4438                 })
4439                 .always(function() {
4440                     if(typeof callback === "function")
4441                         callback();
4442                 });
4443             }
4444         }
4445         else {
4446             NETDATA.chartLibraries.morris.enabled = false;
4447             if(typeof callback === "function")
4448                 callback();
4449         }
4450     };
4451
4452     NETDATA.morrisChartUpdate = function(state, data) {
4453         state.morris_instance.setData(data.result.data);
4454         return true;
4455     };
4456
4457     NETDATA.morrisChartCreate = function(state, data) {
4458
4459         state.morris_options = {
4460                 element: state.element_chart.id,
4461                 data: data.result.data,
4462                 xkey: 'time',
4463                 ykeys: data.dimension_names,
4464                 labels: data.dimension_names,
4465                 lineWidth: 2,
4466                 pointSize: 3,
4467                 smooth: true,
4468                 hideHover: 'auto',
4469                 parseTime: true,
4470                 continuousLine: false,
4471                 behaveLikeLine: false
4472         };
4473
4474         if(state.chart.chart_type === 'line')
4475             state.morris_instance = new Morris.Line(state.morris_options);
4476
4477         else if(state.chart.chart_type === 'area') {
4478             state.morris_options.behaveLikeLine = true;
4479             state.morris_instance = new Morris.Area(state.morris_options);
4480         }
4481         else // stacked
4482             state.morris_instance = new Morris.Area(state.morris_options);
4483
4484         return true;
4485     };
4486
4487     // ----------------------------------------------------------------------------------------------------------------
4488     // raphael
4489
4490     NETDATA.raphaelInitialize = function(callback) {
4491         if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4492             $.ajax({
4493                 url: NETDATA.raphael_js,
4494                 cache: true,
4495                 dataType: "script",
4496                 xhrFields: { withCredentials: true } // required for the cookie
4497             })
4498             .done(function() {
4499                 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4500             })
4501             .fail(function() {
4502                 NETDATA.chartLibraries.raphael.enabled = false;
4503                 NETDATA.error(100, NETDATA.raphael_js);
4504             })
4505             .always(function() {
4506                 if(typeof callback === "function")
4507                     callback();
4508             });
4509         }
4510         else {
4511             NETDATA.chartLibraries.raphael.enabled = false;
4512             if(typeof callback === "function")
4513                 callback();
4514         }
4515     };
4516
4517     NETDATA.raphaelChartUpdate = function(state, data) {
4518         $(state.element_chart).raphael(data.result, {
4519             width: state.chartWidth(),
4520             height: state.chartHeight()
4521         });
4522
4523         return false;
4524     };
4525
4526     NETDATA.raphaelChartCreate = function(state, data) {
4527         $(state.element_chart).raphael(data.result, {
4528             width: state.chartWidth(),
4529             height: state.chartHeight()
4530         });
4531
4532         return false;
4533     };
4534
4535     // ----------------------------------------------------------------------------------------------------------------
4536     // C3
4537
4538     NETDATA.c3Initialize = function(callback) {
4539         if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4540
4541             // C3 requires D3
4542             if(!NETDATA.chartLibraries.d3.initialized) {
4543                 if(NETDATA.chartLibraries.d3.enabled) {
4544                     NETDATA.d3Initialize(function() {
4545                         NETDATA.c3Initialize(callback);
4546                     });
4547                 }
4548                 else {
4549                     NETDATA.chartLibraries.c3.enabled = false;
4550                     if(typeof callback === "function")
4551                         callback();
4552                 }
4553             }
4554             else {
4555                 NETDATA._loadCSS(NETDATA.c3_css);
4556
4557                 $.ajax({
4558                     url: NETDATA.c3_js,
4559                     cache: true,
4560                     dataType: "script",
4561                     xhrFields: { withCredentials: true } // required for the cookie
4562                 })
4563                 .done(function() {
4564                     NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4565                 })
4566                 .fail(function() {
4567                     NETDATA.chartLibraries.c3.enabled = false;
4568                     NETDATA.error(100, NETDATA.c3_js);
4569                 })
4570                 .always(function() {
4571                     if(typeof callback === "function")
4572                         callback();
4573                 });
4574             }
4575         }
4576         else {
4577             NETDATA.chartLibraries.c3.enabled = false;
4578             if(typeof callback === "function")
4579                 callback();
4580         }
4581     };
4582
4583     NETDATA.c3ChartUpdate = function(state, data) {
4584         state.c3_instance.destroy();
4585         return NETDATA.c3ChartCreate(state, data);
4586
4587         //state.c3_instance.load({
4588         //  rows: data.result,
4589         //  unload: true
4590         //});
4591
4592         //return true;
4593     };
4594
4595     NETDATA.c3ChartCreate = function(state, data) {
4596
4597         state.element_chart.id = 'c3-' + state.uuid;
4598         // console.log('id = ' + state.element_chart.id);
4599
4600         state.c3_instance = c3.generate({
4601             bindto: '#' + state.element_chart.id,
4602             size: {
4603                 width: state.chartWidth(),
4604                 height: state.chartHeight()
4605             },
4606             color: {
4607                 pattern: state.chartColors()
4608             },
4609             data: {
4610                 x: 'time',
4611                 rows: data.result,
4612                 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4613             },
4614             axis: {
4615                 x: {
4616                     type: 'timeseries',
4617                     tick: {
4618                         format: function(x) {
4619                             return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4620                         }
4621                     }
4622                 }
4623             },
4624             grid: {
4625                 x: {
4626                     show: true
4627                 },
4628                 y: {
4629                     show: true
4630                 }
4631             },
4632             point: {
4633                 show: false
4634             },
4635             line: {
4636                 connectNull: false
4637             },
4638             transition: {
4639                 duration: 0
4640             },
4641             interaction: {
4642                 enabled: true
4643             }
4644         });
4645
4646         // console.log(state.c3_instance);
4647
4648         return true;
4649     };
4650
4651     // ----------------------------------------------------------------------------------------------------------------
4652     // D3
4653
4654     NETDATA.d3Initialize = function(callback) {
4655         if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4656             $.ajax({
4657                 url: NETDATA.d3_js,
4658                 cache: true,
4659                 dataType: "script",
4660                 xhrFields: { withCredentials: true } // required for the cookie
4661             })
4662             .done(function() {
4663                 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4664             })
4665             .fail(function() {
4666                 NETDATA.chartLibraries.d3.enabled = false;
4667                 NETDATA.error(100, NETDATA.d3_js);
4668             })
4669             .always(function() {
4670                 if(typeof callback === "function")
4671                     callback();
4672             });
4673         }
4674         else {
4675             NETDATA.chartLibraries.d3.enabled = false;
4676             if(typeof callback === "function")
4677                 callback();
4678         }
4679     };
4680
4681     NETDATA.d3ChartUpdate = function(state, data) {
4682         return false;
4683     };
4684
4685     NETDATA.d3ChartCreate = function(state, data) {
4686         return false;
4687     };
4688
4689     // ----------------------------------------------------------------------------------------------------------------
4690     // google charts
4691
4692     NETDATA.googleInitialize = function(callback) {
4693         if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4694             $.ajax({
4695                 url: NETDATA.google_js,
4696                 cache: true,
4697                 dataType: "script",
4698                 xhrFields: { withCredentials: true } // required for the cookie
4699             })
4700             .done(function() {
4701                 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4702                 google.load('visualization', '1.1', {
4703                     'packages': ['corechart', 'controls'],
4704                     'callback': callback
4705                 });
4706             })
4707             .fail(function() {
4708                 NETDATA.chartLibraries.google.enabled = false;
4709                 NETDATA.error(100, NETDATA.google_js);
4710                 if(typeof callback === "function")
4711                     callback();
4712             });
4713         }
4714         else {
4715             NETDATA.chartLibraries.google.enabled = false;
4716             if(typeof callback === "function")
4717                 callback();
4718         }
4719     };
4720
4721     NETDATA.googleChartUpdate = function(state, data) {
4722         var datatable = new google.visualization.DataTable(data.result);
4723         state.google_instance.draw(datatable, state.google_options);
4724         return true;
4725     };
4726
4727     NETDATA.googleChartCreate = function(state, data) {
4728         var datatable = new google.visualization.DataTable(data.result);
4729
4730         state.google_options = {
4731             colors: state.chartColors(),
4732
4733             // do not set width, height - the chart resizes itself
4734             //width: state.chartWidth(),
4735             //height: state.chartHeight(),
4736             lineWidth: 1,
4737             title: state.title,
4738             fontSize: 11,
4739             hAxis: {
4740             //  title: "Time of Day",
4741             //  format:'HH:mm:ss',
4742                 viewWindowMode: 'maximized',
4743                 slantedText: false,
4744                 format:'HH:mm:ss',
4745                 textStyle: {
4746                     fontSize: 9
4747                 },
4748                 gridlines: {
4749                     color: '#EEE'
4750                 }
4751             },
4752             vAxis: {
4753                 title: state.units,
4754                 viewWindowMode: 'pretty',
4755                 minValue: -0.1,
4756                 maxValue: 0.1,
4757                 direction: 1,
4758                 textStyle: {
4759                     fontSize: 9
4760                 },
4761                 gridlines: {
4762                     color: '#EEE'
4763                 }
4764             },
4765             chartArea: {
4766                 width: '65%',
4767                 height: '80%'
4768             },
4769             focusTarget: 'category',
4770             annotation: {
4771                 '1': {
4772                     style: 'line'
4773                 }
4774             },
4775             pointsVisible: 0,
4776             titlePosition: 'out',
4777             titleTextStyle: {
4778                 fontSize: 11
4779             },
4780             tooltip: {
4781                 isHtml: false,
4782                 ignoreBounds: true,
4783                 textStyle: {
4784                     fontSize: 9
4785                 }
4786             },
4787             curveType: 'function',
4788             areaOpacity: 0.3,
4789             isStacked: false
4790         };
4791
4792         switch(state.chart.chart_type) {
4793             case "area":
4794                 state.google_options.vAxis.viewWindowMode = 'maximized';
4795                 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4796                 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4797                 break;
4798
4799             case "stacked":
4800                 state.google_options.isStacked = true;
4801                 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4802                 state.google_options.vAxis.viewWindowMode = 'maximized';
4803                 state.google_options.vAxis.minValue = null;
4804                 state.google_options.vAxis.maxValue = null;
4805                 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4806                 break;
4807
4808             default:
4809             case "line":
4810                 state.google_options.lineWidth = 2;
4811                 state.google_instance = new google.visualization.LineChart(state.element_chart);
4812                 break;
4813         }
4814
4815         state.google_instance.draw(datatable, state.google_options);
4816         return true;
4817     };
4818
4819     // ----------------------------------------------------------------------------------------------------------------
4820
4821     NETDATA.percentFromValueMax = function(value, max) {
4822         if(value === null) value = 0;
4823         if(max < value) max = value;
4824
4825         var pcent = 0;
4826         if(max !== 0) {
4827             pcent = Math.round(value * 100 / max);
4828             if(pcent === 0 && value > 0) pcent = 1;
4829         }
4830
4831         return pcent;
4832     };
4833
4834     // ----------------------------------------------------------------------------------------------------------------
4835     // easy-pie-chart
4836
4837     NETDATA.easypiechartInitialize = function(callback) {
4838         if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4839             $.ajax({
4840                 url: NETDATA.easypiechart_js,
4841                 cache: true,
4842                 dataType: "script",
4843                 xhrFields: { withCredentials: true } // required for the cookie
4844             })
4845                 .done(function() {
4846                     NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4847                 })
4848                 .fail(function() {
4849                     NETDATA.chartLibraries.easypiechart.enabled = false;
4850                     NETDATA.error(100, NETDATA.easypiechart_js);
4851                 })
4852                 .always(function() {
4853                     if(typeof callback === "function")
4854                         callback();
4855                 })
4856         }
4857         else {
4858             NETDATA.chartLibraries.easypiechart.enabled = false;
4859             if(typeof callback === "function")
4860                 callback();
4861         }
4862     };
4863
4864     NETDATA.easypiechartClearSelection = function(state) {
4865         if(typeof state.easyPieChartEvent !== 'undefined') {
4866             if(state.easyPieChartEvent.timer !== null)
4867                 clearTimeout(state.easyPieChartEvent.timer);
4868
4869             state.easyPieChartEvent.timer = null;
4870         }
4871
4872         if(state.isAutoRefreshable() === true && state.data !== null) {
4873             NETDATA.easypiechartChartUpdate(state, state.data);
4874         }
4875         else {
4876             state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4877             state.easyPieChart_instance.update(0);
4878         }
4879         state.easyPieChart_instance.enableAnimation();
4880
4881         return true;
4882     };
4883
4884     NETDATA.easypiechartSetSelection = function(state, t) {
4885         if(state.timeIsVisible(t) !== true)
4886             return NETDATA.easypiechartClearSelection(state);
4887
4888         var slot = state.calculateRowForTime(t);
4889         if(slot < 0 || slot >= state.data.result.length)
4890             return NETDATA.easypiechartClearSelection(state);
4891
4892         if(typeof state.easyPieChartEvent === 'undefined') {
4893             state.easyPieChartEvent = {
4894                 timer: null,
4895                 value: 0,
4896                 pcent: 0
4897             };
4898         }
4899
4900         var value = state.data.result[state.data.result.length - 1 - slot];
4901         var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4902         var pcent = NETDATA.percentFromValueMax(value, max);
4903
4904         state.easyPieChartEvent.value = value;
4905         state.easyPieChartEvent.pcent = pcent;
4906         state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4907
4908         if(state.easyPieChartEvent.timer === null) {
4909             state.easyPieChart_instance.disableAnimation();
4910
4911             state.easyPieChartEvent.timer = setTimeout(function() {
4912                 state.easyPieChartEvent.timer = null;
4913                 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4914             }, NETDATA.options.current.charts_selection_animation_delay);
4915         }
4916
4917         return true;
4918     };
4919
4920     NETDATA.easypiechartChartUpdate = function(state, data) {
4921         var value, max, pcent;
4922
4923         if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
4924             value = null;
4925             max = 0;
4926             pcent = 0;
4927         }
4928         else {
4929             value = data.result[0];
4930             max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4931             pcent = NETDATA.percentFromValueMax(value, max);
4932         }
4933
4934         state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4935         state.easyPieChart_instance.update(pcent);
4936         return true;
4937     };
4938
4939     NETDATA.easypiechartChartCreate = function(state, data) {
4940         var self = $(state.element);
4941         var chart = $(state.element_chart);
4942
4943         var value = data.result[0];
4944         var max = self.data('easypiechart-max-value') || null;
4945         var adjust = self.data('easypiechart-adjust') || null;
4946
4947         if(max === null) {
4948             max = data.max;
4949             state.easyPieChartMax = null;
4950         }
4951         else
4952             state.easyPieChartMax = max;
4953
4954         var pcent = NETDATA.percentFromValueMax(value, max);
4955
4956         chart.data('data-percent', pcent);
4957
4958         var size;
4959         switch(adjust) {
4960             case 'width': size = state.chartHeight(); break;
4961             case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4962             case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4963             case 'height':
4964             default: size = state.chartWidth(); break;
4965         }
4966         state.element.style.width = size + 'px';
4967         state.element.style.height = size + 'px';
4968
4969         var stroke = Math.floor(size / 22);
4970         if(stroke < 3) stroke = 2;
4971
4972         var valuefontsize = Math.floor((size * 2 / 3) / 5);
4973         var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4974         state.easyPieChartLabel = document.createElement('span');
4975         state.easyPieChartLabel.className = 'easyPieChartLabel';
4976         state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4977         state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4978         state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4979         state.element_chart.appendChild(state.easyPieChartLabel);
4980
4981         var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4982         var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4983         state.easyPieChartTitle = document.createElement('span');
4984         state.easyPieChartTitle.className = 'easyPieChartTitle';
4985         state.easyPieChartTitle.innerHTML = state.title;
4986         state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4987         state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4988         state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4989         state.element_chart.appendChild(state.easyPieChartTitle);
4990
4991         var unitfontsize = Math.round(titlefontsize * 0.9);
4992         var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4993         state.easyPieChartUnits = document.createElement('span');
4994         state.easyPieChartUnits.className = 'easyPieChartUnits';
4995         state.easyPieChartUnits.innerHTML = state.units;
4996         state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4997         state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4998         state.element_chart.appendChild(state.easyPieChartUnits);
4999
5000         chart.easyPieChart({
5001             barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
5002             trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5003             scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5004             scaleLength: self.data('easypiechart-scalelength') || 5,
5005             lineCap: self.data('easypiechart-linecap') || 'round',
5006             lineWidth: self.data('easypiechart-linewidth') || stroke,
5007             trackWidth: self.data('easypiechart-trackwidth') || undefined,
5008             size: self.data('easypiechart-size') || size,
5009             rotate: self.data('easypiechart-rotate') || 0,
5010             animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
5011             easing: self.data('easypiechart-easing') || undefined
5012         });
5013
5014         // when we just re-create the chart
5015         // do not animate the first update
5016         var animate = true;
5017         if(typeof state.easyPieChart_instance !== 'undefined')
5018             animate = false;
5019
5020         state.easyPieChart_instance = chart.data('easyPieChart');
5021         if(animate === false) state.easyPieChart_instance.disableAnimation();
5022         state.easyPieChart_instance.update(pcent);
5023         if(animate === false) state.easyPieChart_instance.enableAnimation();
5024         return true;
5025     };
5026
5027     // ----------------------------------------------------------------------------------------------------------------
5028     // gauge.js
5029
5030     NETDATA.gaugeInitialize = function(callback) {
5031         if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5032             $.ajax({
5033                 url: NETDATA.gauge_js,
5034                 cache: true,
5035                 dataType: "script",
5036                 xhrFields: { withCredentials: true } // required for the cookie
5037             })
5038                 .done(function() {
5039                     NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5040                 })
5041                 .fail(function() {
5042                     NETDATA.chartLibraries.gauge.enabled = false;
5043                     NETDATA.error(100, NETDATA.gauge_js);
5044                 })
5045                 .always(function() {
5046                     if(typeof callback === "function")
5047                         callback();
5048                 })
5049         }
5050         else {
5051             NETDATA.chartLibraries.gauge.enabled = false;
5052             if(typeof callback === "function")
5053                 callback();
5054         }
5055     };
5056
5057     NETDATA.gaugeAnimation = function(state, status) {
5058         var speed = 32;
5059
5060         if(typeof status === 'boolean' && status === false)
5061             speed = 1000000000;
5062         else if(typeof status === 'number')
5063             speed = status;
5064
5065         // console.log('gauge speed ' + speed);
5066         state.gauge_instance.animationSpeed = speed;
5067         state.___gaugeOld__.speed = speed;
5068     };
5069
5070     NETDATA.gaugeSet = function(state, value, min, max) {
5071         if(typeof value !== 'number') value = 0;
5072         if(typeof min !== 'number') min = 0;
5073         if(typeof max !== 'number') max = 0;
5074         if(value > max) max = value;
5075         if(value < min) min = value;
5076         if(min > max) {
5077             var t = min;
5078             min = max;
5079             max = t;
5080         }
5081         else if(min == max)
5082             max = min + 1;
5083
5084         // gauge.js has an issue if the needle
5085         // is smaller than min or larger than max
5086         // when we set the new values
5087         // the needle will go crazy
5088
5089         // to prevent it, we always feed it
5090         // with a percentage, so that the needle
5091         // is always between min and max
5092         var pcent = (value - min) * 100 / (max - min);
5093
5094         // these should never happen
5095         if(pcent < 0) pcent = 0;
5096         if(pcent > 100) pcent = 100;
5097
5098         state.gauge_instance.set(pcent);
5099         // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5100
5101         state.___gaugeOld__.value = value;
5102         state.___gaugeOld__.min = min;
5103         state.___gaugeOld__.max = max;
5104     };
5105
5106     NETDATA.gaugeSetLabels = function(state, value, min, max) {
5107         if(state.___gaugeOld__.valueLabel !== value) {
5108             state.___gaugeOld__.valueLabel = value;
5109             state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
5110         }
5111         if(state.___gaugeOld__.minLabel !== min) {
5112             state.___gaugeOld__.minLabel = min;
5113             state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
5114         }
5115         if(state.___gaugeOld__.maxLabel !== max) {
5116             state.___gaugeOld__.maxLabel = max;
5117             state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
5118         }
5119     };
5120
5121     NETDATA.gaugeClearSelection = function(state) {
5122         if(typeof state.gaugeEvent !== 'undefined') {
5123             if(state.gaugeEvent.timer !== null)
5124                 clearTimeout(state.gaugeEvent.timer);
5125
5126             state.gaugeEvent.timer = null;
5127         }
5128
5129         if(state.isAutoRefreshable() === true && state.data !== null) {
5130             NETDATA.gaugeChartUpdate(state, state.data);
5131         }
5132         else {
5133             NETDATA.gaugeAnimation(state, false);
5134             NETDATA.gaugeSet(state, null, null, null);
5135             NETDATA.gaugeSetLabels(state, null, null, null);
5136         }
5137
5138         NETDATA.gaugeAnimation(state, true);
5139         return true;
5140     };
5141
5142     NETDATA.gaugeSetSelection = function(state, t) {
5143         if(state.timeIsVisible(t) !== true)
5144             return NETDATA.gaugeClearSelection(state);
5145
5146         var slot = state.calculateRowForTime(t);
5147         if(slot < 0 || slot >= state.data.result.length)
5148             return NETDATA.gaugeClearSelection(state);
5149
5150         if(typeof state.gaugeEvent === 'undefined') {
5151             state.gaugeEvent = {
5152                 timer: null,
5153                 value: 0,
5154                 min: 0,
5155                 max: 0
5156             };
5157         }
5158
5159         var value = state.data.result[state.data.result.length - 1 - slot];
5160         var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
5161         var min = 0;
5162
5163         state.gaugeEvent.value = value;
5164         state.gaugeEvent.max = max;
5165         state.gaugeEvent.min = min;
5166         NETDATA.gaugeSetLabels(state, value, min, max);
5167
5168         if(state.gaugeEvent.timer === null) {
5169             NETDATA.gaugeAnimation(state, false);
5170
5171             state.gaugeEvent.timer = setTimeout(function() {
5172                 state.gaugeEvent.timer = null;
5173                 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5174             }, NETDATA.options.current.charts_selection_animation_delay);
5175         }
5176
5177         return true;
5178     };
5179
5180     NETDATA.gaugeChartUpdate = function(state, data) {
5181         var value, min, max;
5182
5183         if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5184             value = 0;
5185             min = 0;
5186             max = 1;
5187             NETDATA.gaugeSetLabels(state, null, null, null);
5188         }
5189         else {
5190             value = data.result[0];
5191             min = 0;
5192             max = (state.gaugeMax === null)?data.max:state.gaugeMax;
5193             if(value > max) max = value;
5194             NETDATA.gaugeSetLabels(state, value, min, max);
5195         }
5196
5197         NETDATA.gaugeSet(state, value, min, max);
5198         return true;
5199     };
5200
5201     NETDATA.gaugeChartCreate = function(state, data) {
5202         var self = $(state.element);
5203         // var chart = $(state.element_chart);
5204
5205         var value = data.result[0];
5206         var max = self.data('gauge-max-value') || null;
5207         var adjust = self.data('gauge-adjust') || null;
5208         var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5209         var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5210         var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5211         var stopColor = self.data('gauge-stop-color') || void 0;
5212         var generateGradient = self.data('gauge-generate-gradient') || false;
5213
5214         if(max === null) {
5215             max = data.max;
5216             state.gaugeMax = null;
5217         }
5218         else
5219             state.gaugeMax = max;
5220
5221         var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5222         //switch(adjust) {
5223         //  case 'width': width = height * ratio; break;
5224         //  case 'height':
5225         //  default: height = width / ratio; break;
5226         //}
5227         //state.element.style.width = width.toString() + 'px';
5228         //state.element.style.height = height.toString() + 'px';
5229
5230         var lum_d = 0.05;
5231
5232         var options = {
5233             lines: 12,                  // The number of lines to draw
5234             angle: 0.15,                // The length of each line
5235             lineWidth: 0.44,            // 0.44 The line thickness
5236             pointer: {
5237                 length: 0.8,            // 0.9 The radius of the inner circle
5238                 strokeWidth: 0.035,     // The rotation offset
5239                 color: pointerColor     // Fill color
5240             },
5241             colorStart: startColor,     // Colors
5242             colorStop: stopColor,       // just experiment with them
5243             strokeColor: strokeColor,   // to see which ones work best for you
5244             limitMax: true,
5245             generateGradient: (generateGradient === true)?true:false,
5246             gradientType: 0
5247         };
5248
5249         if (generateGradient.constructor === Array) {
5250             // example options:
5251             // data-gauge-generate-gradient="[0, 50, 100]"
5252             // data-gauge-gradient-percent-color-0="#FFFFFF"
5253             // data-gauge-gradient-percent-color-50="#999900"
5254             // data-gauge-gradient-percent-color-100="#000000"
5255
5256             options.percentColors = new Array();
5257             var len = generateGradient.length;
5258             while(len--) {
5259                 var pcent = generateGradient[len];
5260                 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5261                 if(color !== false) {
5262                     var a = new Array();
5263                     a[0] = pcent / 100;
5264                     a[1] = color;
5265                     options.percentColors.unshift(a);
5266                 }
5267             }
5268             if(options.percentColors.length === 0)
5269                 delete options.percentColors;
5270         }
5271         else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5272             options.percentColors = [
5273                 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5274                 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5275                 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5276                 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5277                 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5278                 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5279                 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5280                 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5281                 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5282                 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5283                 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5284         }
5285
5286         state.gauge_canvas = document.createElement('canvas');
5287         state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5288         state.gauge_canvas.className = 'gaugeChart';
5289         state.gauge_canvas.width  = width;
5290         state.gauge_canvas.height = height;
5291         state.element_chart.appendChild(state.gauge_canvas);
5292
5293         var valuefontsize = Math.floor(height / 6);
5294         var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5295         state.gaugeChartLabel = document.createElement('span');
5296         state.gaugeChartLabel.className = 'gaugeChartLabel';
5297         state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5298         state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5299         state.element_chart.appendChild(state.gaugeChartLabel);
5300
5301         var titlefontsize = Math.round(valuefontsize / 2);
5302         var titletop = 0;
5303         state.gaugeChartTitle = document.createElement('span');
5304         state.gaugeChartTitle.className = 'gaugeChartTitle';
5305         state.gaugeChartTitle.innerHTML = state.title;
5306         state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5307         state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5308         state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5309         state.element_chart.appendChild(state.gaugeChartTitle);
5310
5311         var unitfontsize = Math.round(titlefontsize * 0.9);
5312         state.gaugeChartUnits = document.createElement('span');
5313         state.gaugeChartUnits.className = 'gaugeChartUnits';
5314         state.gaugeChartUnits.innerHTML = state.units;
5315         state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5316         state.element_chart.appendChild(state.gaugeChartUnits);
5317
5318         state.gaugeChartMin = document.createElement('span');
5319         state.gaugeChartMin.className = 'gaugeChartMin';
5320         state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5321         state.element_chart.appendChild(state.gaugeChartMin);
5322
5323         state.gaugeChartMax = document.createElement('span');
5324         state.gaugeChartMax.className = 'gaugeChartMax';
5325         state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5326         state.element_chart.appendChild(state.gaugeChartMax);
5327
5328         // when we just re-create the chart
5329         // do not animate the first update
5330         var animate = true;
5331         if(typeof state.gauge_instance !== 'undefined')
5332             animate = false;
5333
5334         state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5335
5336         state.___gaugeOld__ = {
5337             value: value,
5338             min: 0,
5339             max: max,
5340             valueLabel: null,
5341             minLabel: null,
5342             maxLabel: null
5343         };
5344
5345         // we will always feed a percentage
5346         state.gauge_instance.minValue = 0;
5347         state.gauge_instance.maxValue = 100;
5348
5349         NETDATA.gaugeAnimation(state, animate);
5350         NETDATA.gaugeSet(state, value, 0, max);
5351         NETDATA.gaugeSetLabels(state, value, 0, max);
5352         NETDATA.gaugeAnimation(state, true);
5353         return true;
5354     };
5355
5356     // ----------------------------------------------------------------------------------------------------------------
5357     // Charts Libraries Registration
5358
5359     NETDATA.chartLibraries = {
5360         "dygraph": {
5361             initialize: NETDATA.dygraphInitialize,
5362             create: NETDATA.dygraphChartCreate,
5363             update: NETDATA.dygraphChartUpdate,
5364             resize: function(state) {
5365                 if(typeof state.dygraph_instance.resize === 'function')
5366                     state.dygraph_instance.resize();
5367             },
5368             setSelection: NETDATA.dygraphSetSelection,
5369             clearSelection:  NETDATA.dygraphClearSelection,
5370             toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5371             initialized: false,
5372             enabled: true,
5373             format: function(state) { return 'json'; },
5374             options: function(state) { return 'ms|flip'; },
5375             legend: function(state) {
5376                 if(this.isSparkline(state) === false)
5377                     return 'right-side';
5378                 else
5379                     return null;
5380             },
5381             autoresize: function(state) { return true; },
5382             max_updates_to_recreate: function(state) { return 5000; },
5383             track_colors: function(state) { return true; },
5384             pixels_per_point: function(state) {
5385                 if(this.isSparkline(state) === false)
5386                     return 3;
5387                 else
5388                     return 2;
5389             },
5390
5391             isSparkline: function(state) {
5392                 if(typeof state.dygraph_sparkline === 'undefined') {
5393                     var t = $(state.element).data('dygraph-theme');
5394                     if(t === 'sparkline')
5395                         state.dygraph_sparkline = true;
5396                     else
5397                         state.dygraph_sparkline = false;
5398                 }
5399                 return state.dygraph_sparkline;
5400             }
5401         },
5402         "sparkline": {
5403             initialize: NETDATA.sparklineInitialize,
5404             create: NETDATA.sparklineChartCreate,
5405             update: NETDATA.sparklineChartUpdate,
5406             resize: null,
5407             setSelection: undefined, // function(state, t) { return true; },
5408             clearSelection: undefined, // function(state) { return true; },
5409             toolboxPanAndZoom: null,
5410             initialized: false,
5411             enabled: true,
5412             format: function(state) { return 'array'; },
5413             options: function(state) { return 'flip|abs'; },
5414             legend: function(state) { return null; },
5415             autoresize: function(state) { return false; },
5416             max_updates_to_recreate: function(state) { return 5000; },
5417             track_colors: function(state) { return false; },
5418             pixels_per_point: function(state) { return 3; }
5419         },
5420         "peity": {
5421             initialize: NETDATA.peityInitialize,
5422             create: NETDATA.peityChartCreate,
5423             update: NETDATA.peityChartUpdate,
5424             resize: null,
5425             setSelection: undefined, // function(state, t) { return true; },
5426             clearSelection: undefined, // function(state) { return true; },
5427             toolboxPanAndZoom: null,
5428             initialized: false,
5429             enabled: true,
5430             format: function(state) { return 'ssvcomma'; },
5431             options: function(state) { return 'null2zero|flip|abs'; },
5432             legend: function(state) { return null; },
5433             autoresize: function(state) { return false; },
5434             max_updates_to_recreate: function(state) { return 5000; },
5435             track_colors: function(state) { return false; },
5436             pixels_per_point: function(state) { return 3; }
5437         },
5438         "morris": {
5439             initialize: NETDATA.morrisInitialize,
5440             create: NETDATA.morrisChartCreate,
5441             update: NETDATA.morrisChartUpdate,
5442             resize: null,
5443             setSelection: undefined, // function(state, t) { return true; },
5444             clearSelection: undefined, // function(state) { return true; },
5445             toolboxPanAndZoom: null,
5446             initialized: false,
5447             enabled: true,
5448             format: function(state) { return 'json'; },
5449             options: function(state) { return 'objectrows|ms'; },
5450             legend: function(state) { return null; },
5451             autoresize: function(state) { return false; },
5452             max_updates_to_recreate: function(state) { return 50; },
5453             track_colors: function(state) { return false; },
5454             pixels_per_point: function(state) { return 15; }
5455         },
5456         "google": {
5457             initialize: NETDATA.googleInitialize,
5458             create: NETDATA.googleChartCreate,
5459             update: NETDATA.googleChartUpdate,
5460             resize: null,
5461             setSelection: undefined, //function(state, t) { return true; },
5462             clearSelection: undefined, //function(state) { return true; },
5463             toolboxPanAndZoom: null,
5464             initialized: false,
5465             enabled: true,
5466             format: function(state) { return 'datatable'; },
5467             options: function(state) { return ''; },
5468             legend: function(state) { return null; },
5469             autoresize: function(state) { return false; },
5470             max_updates_to_recreate: function(state) { return 300; },
5471             track_colors: function(state) { return false; },
5472             pixels_per_point: function(state) { return 4; }
5473         },
5474         "raphael": {
5475             initialize: NETDATA.raphaelInitialize,
5476             create: NETDATA.raphaelChartCreate,
5477             update: NETDATA.raphaelChartUpdate,
5478             resize: null,
5479             setSelection: undefined, // function(state, t) { return true; },
5480             clearSelection: undefined, // function(state) { return true; },
5481             toolboxPanAndZoom: null,
5482             initialized: false,
5483             enabled: true,
5484             format: function(state) { return 'json'; },
5485             options: function(state) { return ''; },
5486             legend: function(state) { return null; },
5487             autoresize: function(state) { return false; },
5488             max_updates_to_recreate: function(state) { return 5000; },
5489             track_colors: function(state) { return false; },
5490             pixels_per_point: function(state) { return 3; }
5491         },
5492         "c3": {
5493             initialize: NETDATA.c3Initialize,
5494             create: NETDATA.c3ChartCreate,
5495             update: NETDATA.c3ChartUpdate,
5496             resize: null,
5497             setSelection: undefined, // function(state, t) { return true; },
5498             clearSelection: undefined, // function(state) { return true; },
5499             toolboxPanAndZoom: null,
5500             initialized: false,
5501             enabled: true,
5502             format: function(state) { return 'csvjsonarray'; },
5503             options: function(state) { return 'milliseconds'; },
5504             legend: function(state) { return null; },
5505             autoresize: function(state) { return false; },
5506             max_updates_to_recreate: function(state) { return 5000; },
5507             track_colors: function(state) { return false; },
5508             pixels_per_point: function(state) { return 15; }
5509         },
5510         "d3": {
5511             initialize: NETDATA.d3Initialize,
5512             create: NETDATA.d3ChartCreate,
5513             update: NETDATA.d3ChartUpdate,
5514             resize: null,
5515             setSelection: undefined, // function(state, t) { return true; },
5516             clearSelection: undefined, // function(state) { return true; },
5517             toolboxPanAndZoom: null,
5518             initialized: false,
5519             enabled: true,
5520             format: function(state) { return 'json'; },
5521             options: function(state) { return ''; },
5522             legend: function(state) { return null; },
5523             autoresize: function(state) { return false; },
5524             max_updates_to_recreate: function(state) { return 5000; },
5525             track_colors: function(state) { return false; },
5526             pixels_per_point: function(state) { return 3; }
5527         },
5528         "easypiechart": {
5529             initialize: NETDATA.easypiechartInitialize,
5530             create: NETDATA.easypiechartChartCreate,
5531             update: NETDATA.easypiechartChartUpdate,
5532             resize: null,
5533             setSelection: NETDATA.easypiechartSetSelection,
5534             clearSelection: NETDATA.easypiechartClearSelection,
5535             toolboxPanAndZoom: null,
5536             initialized: false,
5537             enabled: true,
5538             format: function(state) { return 'array'; },
5539             options: function(state) { return 'absolute'; },
5540             legend: function(state) { return null; },
5541             autoresize: function(state) { return false; },
5542             max_updates_to_recreate: function(state) { return 5000; },
5543             track_colors: function(state) { return true; },
5544             pixels_per_point: function(state) { return 3; },
5545             aspect_ratio: 100
5546         },
5547         "gauge": {
5548             initialize: NETDATA.gaugeInitialize,
5549             create: NETDATA.gaugeChartCreate,
5550             update: NETDATA.gaugeChartUpdate,
5551             resize: null,
5552             setSelection: NETDATA.gaugeSetSelection,
5553             clearSelection: NETDATA.gaugeClearSelection,
5554             toolboxPanAndZoom: null,
5555             initialized: false,
5556             enabled: true,
5557             format: function(state) { return 'array'; },
5558             options: function(state) { return 'absolute'; },
5559             legend: function(state) { return null; },
5560             autoresize: function(state) { return false; },
5561             max_updates_to_recreate: function(state) { return 5000; },
5562             track_colors: function(state) { return true; },
5563             pixels_per_point: function(state) { return 3; },
5564             aspect_ratio: 70
5565         }
5566     };
5567
5568     NETDATA.registerChartLibrary = function(library, url) {
5569         if(NETDATA.options.debug.libraries === true)
5570             console.log("registering chart library: " + library);
5571
5572         NETDATA.chartLibraries[library].url = url;
5573         NETDATA.chartLibraries[library].initialized = true;
5574         NETDATA.chartLibraries[library].enabled = true;
5575     };
5576
5577     // ----------------------------------------------------------------------------------------------------------------
5578     // Load required JS libraries and CSS
5579
5580     NETDATA.requiredJs = [
5581         {
5582             url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5583             async: false,
5584             isAlreadyLoaded: function() {
5585                 // check if bootstrap is loaded
5586                 if(typeof $().emulateTransitionEnd == 'function')
5587                     return true;
5588                 else {
5589                     if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5590                         return true;
5591                     else
5592                         return false;
5593                 }
5594             }
5595         },
5596         {
5597             url: NETDATA.serverDefault + 'lib/jquery.nanoscroller-0.8.7.min.js',
5598             isAlreadyLoaded: function() { return false; }
5599         }
5600     ];
5601
5602     NETDATA.requiredCSS = [
5603         {
5604             url: NETDATA.themes.current.bootstrap_css,
5605             isAlreadyLoaded: function() {
5606                 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5607                     return true;
5608                 else
5609                     return false;
5610             }
5611         },
5612         {
5613             url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.6.3',
5614             isAlreadyLoaded: function() { return false; }
5615         },
5616         {
5617             url: NETDATA.themes.current.dashboard_css,
5618             isAlreadyLoaded: function() { return false; }
5619         }
5620     ];
5621
5622     NETDATA.loadedRequiredJs = 0;
5623     NETDATA.loadRequiredJs = function(index, callback) {
5624         if(index >= NETDATA.requiredJs.length) {
5625             if(typeof callback === 'function')
5626                 callback();
5627             return;
5628         }
5629
5630         if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5631             NETDATA.loadedRequiredJs++;
5632             NETDATA.loadRequiredJs(++index, callback);
5633             return;
5634         }
5635
5636         if(NETDATA.options.debug.main_loop === true)
5637             console.log('loading ' + NETDATA.requiredJs[index].url);
5638
5639         var async = true;
5640         if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5641             async = false;
5642
5643         $.ajax({
5644             url: NETDATA.requiredJs[index].url,
5645             cache: true,
5646             dataType: "script",
5647             xhrFields: { withCredentials: true } // required for the cookie
5648         })
5649         .done(function() {
5650             if(NETDATA.options.debug.main_loop === true)
5651                 console.log('loaded ' + NETDATA.requiredJs[index].url);
5652         })
5653         .fail(function() {
5654             alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5655         })
5656         .always(function() {
5657             NETDATA.loadedRequiredJs++;
5658
5659             if(async === false)
5660                 NETDATA.loadRequiredJs(++index, callback);
5661         })
5662
5663         if(async === true)
5664             NETDATA.loadRequiredJs(++index, callback);
5665     };
5666
5667     NETDATA.loadRequiredCSS = function(index) {
5668         if(index >= NETDATA.requiredCSS.length)
5669             return;
5670
5671         if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5672             NETDATA.loadRequiredCSS(++index);
5673             return;
5674         }
5675
5676         if(NETDATA.options.debug.main_loop === true)
5677             console.log('loading ' + NETDATA.requiredCSS[index].url);
5678
5679         NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5680         NETDATA.loadRequiredCSS(++index);
5681     };
5682
5683
5684     // ----------------------------------------------------------------------------------------------------------------
5685     // Registry of netdata hosts
5686
5687     NETDATA.alarms = {
5688         onclick: null,                  // the callback to handle the click - it will be called with the alarm log entry
5689         chart_div_offset: 100,          // give that space above the chart when scrolling to it
5690         chart_div_id_prefix: 'chart_',  // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
5691         chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
5692
5693         ms_penalty: 0,                  // the time penalty of the next alarm
5694         ms_between_notifications: 500,  // firefox moves the alarms off-screen (above, outside the top of the screen)
5695                                         // if alarms are shown faster than: one per 500ms
5696
5697         notifications: false,           // when true, the browser supports notifications (may not be granted though)
5698         last_notification_id: 0,        // the id of the last alarm_log we have raised an alarm for
5699         first_notification_id: 0,       // the id of the first alarm_log entry for this session
5700                                         // this is used to prevent CLEAR notifications for past events
5701         // notifications_shown: new Array(),
5702
5703         server: null,                   // the server to connect to for fetching alarms
5704         current: null,                  // the list of raised alarms - updated in the background
5705         callback: null,                 // a callback function to call every time the list of raised alarms is refreshed
5706
5707         notify: function(entry) {
5708             // console.log('alarm ' + entry.unique_id);
5709
5710             if(entry.updated === true) {
5711                 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
5712                 return;
5713             }
5714
5715             var value = entry.value;
5716             if(NETDATA.alarms.current !== null) {
5717                 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
5718                 if(typeof t !== 'undefined' && entry.status == t.status)
5719                     value = t.value;
5720             }
5721
5722             var name = entry.name.replace(/_/g, ' ');
5723             var status = entry.status.toLowerCase();
5724             var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
5725             var tag = entry.alarm_id;
5726             var icon = 'images/seo-performance-128.png';
5727             var interaction = false;
5728             var data = entry;
5729             var show = true;
5730
5731             // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' +  entry.status);
5732
5733             switch(entry.status) {
5734                 case 'REMOVED':
5735                     show = false;
5736                     break;
5737
5738                 case 'UNDEFINED':
5739                     return;
5740
5741                 case 'UNINITIALIZED':
5742                     return;
5743
5744                 case 'CLEAR':
5745                     if(entry.unique_id < NETDATA.alarms.first_notification_id) {
5746                         // console.log('alarm ' + entry.unique_id + ' is not current');
5747                         return;
5748                     }
5749                     if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
5750                         // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
5751                         return;
5752                     }
5753                     title = name + ' back to normal';
5754                     icon = 'images/check-mark-2-128-green.png'
5755                     interaction = false;
5756                     break;
5757
5758                 case 'WARNING':
5759                     if(entry.old_status === 'CRITICAL')
5760                         status = 'demoted to ' + entry.status.toLowerCase();
5761
5762                     icon = 'images/alert-128-orange.png';
5763                     interaction = false;
5764                     break;
5765
5766                 case 'CRITICAL':
5767                     if(entry.old_status === 'WARNING')
5768                         status = 'escalated to ' + entry.status.toLowerCase();
5769                     
5770                     icon = 'images/alert-128-red.png'
5771                     interaction = true;
5772                     break;
5773
5774                 default:
5775                     console.log('invalid alarm status ' + entry.status);
5776                     return;
5777             }
5778
5779             /*
5780             // cleanup old notifications with the same alarm_id as this one
5781             // FIXME: it does not seem to work on any web browser!
5782             var len = NETDATA.alarms.notifications_shown.length;
5783             while(len--) {
5784                 var n = NETDATA.alarms.notifications_shown[len];
5785                 if(n.data.alarm_id === entry.alarm_id) {
5786                     console.log('removing old alarm ' + n.data.unique_id);
5787
5788                     // close the notification
5789                     n.close.bind(n);
5790
5791                     // remove it from the array
5792                     NETDATA.alarms.notifications_shown.splice(len, 1);
5793                     len = NETDATA.alarms.notifications_shown.length;
5794                 }
5795             }
5796             */
5797
5798             if(show === true) {
5799
5800                 setTimeout(function() {
5801                     // show this notification
5802                     // console.log('new notification: ' + title);
5803                     var n = new Notification(title, {
5804                         body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
5805                         tag: tag,
5806                         requireInteraction: interaction,
5807                         icon: NETDATA.serverDefault + icon,
5808                         data: data
5809                     });
5810
5811                     n.onclick = function(event) {
5812                         event.preventDefault();
5813                         NETDATA.alarms.onclick(event.target.data);
5814                     };
5815
5816                     // console.log(n);
5817                     // NETDATA.alarms.notifications_shown.push(n);
5818                     // console.log(entry);
5819                 }, NETDATA.alarms.ms_penalty);
5820
5821                 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
5822             }
5823         },
5824
5825         scrollToChart: function(chart_id) {
5826             if(typeof chart_id === 'string') {
5827                 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
5828                 if(typeof offset !== 'undefined') {
5829                     $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
5830                     return true;
5831                 }
5832             }
5833             return false;
5834         },
5835
5836         scrollToAlarm: function(alarm) {
5837             if(typeof alarm === 'object') {
5838                 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
5839
5840                 if(ret === true && NETDATA.options.page_is_visible === false)
5841                     window.focus();
5842                 //    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.');
5843             }
5844
5845         },
5846
5847         notifyAll: function() {
5848             // console.log('FETCHING ALARM LOG');
5849             NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
5850                 // console.log('ALARM LOG FETCHED');
5851
5852                 if(data === null || typeof data !== 'object') {
5853                     console.log('invalid alarms log response');
5854                     return;
5855                 }
5856
5857                 if(data.length === 0) {
5858                     console.log('received empty alarm log');
5859                     return;
5860                 }
5861
5862                 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
5863
5864                 data.sort(function(a, b) {
5865                     if(a.unique_id > b.unique_id) return -1;
5866                     if(a.unique_id < b.unique_id) return 1;
5867                     return 0;
5868                 });
5869
5870                 NETDATA.alarms.ms_penalty = 0;
5871
5872                 var len = data.length;
5873                 while(len--) {
5874                     if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
5875                         NETDATA.alarms.notify(data[len]);
5876                     }
5877                     //else
5878                     //    console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
5879                 }
5880
5881                 NETDATA.alarms.last_notification_id = data[0].unique_id;
5882                 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
5883                 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
5884             })
5885         },
5886
5887         check_notifications: function() {
5888             // returns true if we should fire 1+ notifications
5889
5890             if(NETDATA.alarms.notifications !== true) {
5891                 // console.log('notifications not available');
5892                 return false;
5893             }
5894
5895             if(Notification.permission !== 'granted') {
5896                 // console.log('notifications not granted');
5897                 return false;
5898             }
5899
5900             if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
5901                 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
5902
5903                 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
5904                     // console.log('new alarms detected');
5905                     return true;
5906                 }
5907                 //else console.log('no new alarms');
5908             }
5909             // else console.log('cannot process alarms');
5910
5911             return false;
5912         },
5913
5914         get: function(what, callback) {
5915             $.ajax({
5916                 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
5917                 async: true,
5918                 cache: false,
5919                 xhrFields: { withCredentials: true } // required for the cookie
5920             })
5921                 .done(function(data) {
5922                     if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
5923                         NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
5924
5925                     if(typeof callback === 'function')
5926                         callback(data);
5927                 })
5928                 .fail(function() {
5929                     NETDATA.error(415, NETDATA.alarms.server);
5930
5931                     if(typeof callback === 'function')
5932                         callback(null);
5933                 });
5934         },
5935
5936         update_forever: function() {
5937             NETDATA.alarms.get('active', function(data) {
5938                 if(data !== null) {
5939                     NETDATA.alarms.current = data;
5940
5941                     if(NETDATA.alarms.check_notifications() === true) {
5942                         NETDATA.alarms.notifyAll();
5943                     }
5944
5945                     if (typeof NETDATA.alarms.callback === 'function') {
5946                         NETDATA.alarms.callback(data);
5947                     }
5948
5949                     // Health monitoring is disabled on this netdata
5950                     if(data.status === false) return;
5951                 }
5952
5953                 setTimeout(NETDATA.alarms.update_forever, 10000);
5954             });
5955         },
5956
5957         get_log: function(last_id, callback) {
5958             // console.log('fetching all log after ' + last_id.toString());
5959             $.ajax({
5960                 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
5961                 async: true,
5962                 cache: false,
5963                 xhrFields: { withCredentials: true } // required for the cookie
5964             })
5965                 .done(function(data) {
5966                     if(typeof callback === 'function')
5967                         callback(data);
5968                 })
5969                 .fail(function() {
5970                     NETDATA.error(416, NETDATA.alarms.server);
5971
5972                     if(typeof callback === 'function')
5973                         callback(null);
5974                 });
5975         },
5976
5977         init: function() {
5978             var host = NETDATA.serverDefault;
5979             while(host.slice(-1) === '/')
5980                 host = host.substring(0, host.length - 1);
5981             NETDATA.alarms.server = host;
5982
5983             NETDATA.alarms.last_notification_id = NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
5984
5985             if(NETDATA.alarms.onclick === null)
5986                 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
5987
5988             if(netdataShowAlarms === true) {
5989                 NETDATA.alarms.update_forever();
5990             
5991                 if('Notification' in window) {
5992                     // console.log('notifications available');
5993                     NETDATA.alarms.notifications = true;
5994
5995                     if(Notification.permission === 'default')
5996                         Notification.requestPermission();
5997                 }
5998             }
5999         }
6000     };
6001
6002     // ----------------------------------------------------------------------------------------------------------------
6003     // Registry of netdata hosts
6004
6005     NETDATA.registry = {
6006         server: null,       // the netdata registry server
6007         person_guid: null,  // the unique ID of this browser / user
6008         machine_guid: null, // the unique ID the netdata server that served dashboard.js
6009         hostname: null,     // the hostname of the netdata server that served dashboard.js
6010         machines: null,         // the user's other URLs
6011         machines_array: null,   // the user's other URLs in an array
6012         person_urls: null,
6013
6014         parsePersonUrls: function(person_urls) {
6015             // console.log(person_urls);
6016             NETDATA.registry.person_urls = person_urls;
6017
6018             if(person_urls) {
6019                 NETDATA.registry.machines = {};
6020                 NETDATA.registry.machines_array = new Array();
6021
6022                 var now = new Date().getTime();
6023                 var apu = person_urls;
6024                 var i = apu.length;
6025                 while(i--) {
6026                     if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6027                         // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6028
6029                         var obj = {
6030                             guid: apu[i][0],
6031                             url: apu[i][1],
6032                             last_t: apu[i][2],
6033                             accesses: apu[i][3],
6034                             name: apu[i][4],
6035                             alternate_urls: new Array()
6036                         };
6037                         obj.alternate_urls.push(apu[i][1]);
6038
6039                         NETDATA.registry.machines[apu[i][0]] = obj;
6040                         NETDATA.registry.machines_array.push(obj);
6041                     }
6042                     else {
6043                         // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6044
6045                         var pu = NETDATA.registry.machines[apu[i][0]];
6046                         if(pu.last_t < apu[i][2]) {
6047                             pu.url = apu[i][1];
6048                             pu.last_t = apu[i][2];
6049                             pu.name = apu[i][4];
6050                         }
6051                         pu.accesses += apu[i][3];
6052                         pu.alternate_urls.push(apu[i][1]);
6053                     }
6054                 }
6055             }
6056
6057             if(typeof netdataRegistryCallback === 'function')
6058                 netdataRegistryCallback(NETDATA.registry.machines_array);
6059         },
6060
6061         init: function() {
6062             if(netdataRegistry !== true) return;
6063
6064             NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6065                 if(data) {
6066                     NETDATA.registry.server = data.registry;
6067                     NETDATA.registry.machine_guid = data.machine_guid;
6068                     NETDATA.registry.hostname = data.hostname;
6069
6070                     NETDATA.registry.access(2, function (person_urls) {
6071                         NETDATA.registry.parsePersonUrls(person_urls);
6072
6073                     });
6074                 }
6075             });
6076         },
6077
6078         hello: function(host, callback) {
6079             while(host.slice(-1) === '/')
6080                 host = host.substring(0, host.length - 1);
6081
6082             // send HELLO to a netdata server:
6083             // 1. verifies the server is reachable
6084             // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6085             $.ajax({
6086                     url: host + '/api/v1/registry?action=hello',
6087                     async: true,
6088                     cache: false,
6089                     xhrFields: { withCredentials: true } // required for the cookie
6090                 })
6091                 .done(function(data) {
6092                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6093                         NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6094                         data = null;
6095                     }
6096
6097                     if(typeof callback === 'function')
6098                         callback(data);
6099                 })
6100                 .fail(function() {
6101                     NETDATA.error(407, host);
6102
6103                     if(typeof callback === 'function')
6104                         callback(null);
6105                 });
6106         },
6107
6108         access: function(max_redirects, callback) {
6109             // send ACCESS to a netdata registry:
6110             // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6111             // 2. it responds with a list of netdata servers we know
6112             // the registry identifies us using a cookie it sets the first time we access it
6113             // the registry may respond with a redirect URL to send us to another registry
6114             $.ajax({
6115                     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),
6116                     async: true,
6117                     cache: false,
6118                     xhrFields: { withCredentials: true } // required for the cookie
6119                 })
6120                 .done(function(data) {
6121                     var redirect = null;
6122                     if(typeof data.registry === 'string')
6123                         redirect = data.registry;
6124
6125                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6126                         NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6127                         data = null;
6128                     }
6129
6130                     if(data === null) {
6131                         if(redirect !== null && max_redirects > 0) {
6132                             NETDATA.registry.server = redirect;
6133                             NETDATA.registry.access(max_redirects - 1, callback);
6134                         }
6135                         else {
6136                             if(typeof callback === 'function')
6137                                 callback(null);
6138                         }
6139                     }
6140                     else {
6141                         if(typeof data.person_guid === 'string')
6142                             NETDATA.registry.person_guid = data.person_guid;
6143
6144                         if(typeof callback === 'function')
6145                             callback(data.urls);
6146                     }
6147                 })
6148                 .fail(function() {
6149                     NETDATA.error(410, NETDATA.registry.server);
6150
6151                     if(typeof callback === 'function')
6152                         callback(null);
6153                 });
6154         },
6155
6156         delete: function(delete_url, callback) {
6157             // send DELETE to a netdata registry:
6158             $.ajax({
6159                 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),
6160                 async: true,
6161                 cache: false,
6162                 xhrFields: { withCredentials: true } // required for the cookie
6163             })
6164                 .done(function(data) {
6165                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6166                         NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6167                         data = null;
6168                     }
6169
6170                     if(typeof callback === 'function')
6171                         callback(data);
6172                 })
6173                 .fail(function() {
6174                     NETDATA.error(412, NETDATA.registry.server);
6175
6176                     if(typeof callback === 'function')
6177                         callback(null);
6178                 });
6179         },
6180
6181         search: function(machine_guid, callback) {
6182             // SEARCH for the URLs of a machine:
6183             $.ajax({
6184                 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,
6185                 async: true,
6186                 cache: false,
6187                 xhrFields: { withCredentials: true } // required for the cookie
6188             })
6189                 .done(function(data) {
6190                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6191                         NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6192                         data = null;
6193                     }
6194
6195                     if(typeof callback === 'function')
6196                         callback(data);
6197                 })
6198                 .fail(function() {
6199                     NETDATA.error(418, NETDATA.registry.server);
6200
6201                     if(typeof callback === 'function')
6202                         callback(null);
6203                 });
6204         },
6205
6206         switch: function(new_person_guid, callback) {
6207             // impersonate
6208             $.ajax({
6209                 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,
6210                 async: true,
6211                 cache: false,
6212                 xhrFields: { withCredentials: true } // required for the cookie
6213             })
6214                 .done(function(data) {
6215                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6216                         NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6217                         data = null;
6218                     }
6219
6220                     if(typeof callback === 'function')
6221                         callback(data);
6222                 })
6223                 .fail(function() {
6224                     NETDATA.error(414, NETDATA.registry.server);
6225
6226                     if(typeof callback === 'function')
6227                         callback(null);
6228                 });
6229         }
6230     };
6231
6232     // ----------------------------------------------------------------------------------------------------------------
6233     // Boot it!
6234
6235     if(typeof netdataPrepCallback === 'function')
6236         netdataPrepCallback();
6237
6238     NETDATA.errorReset();
6239     NETDATA.loadRequiredCSS(0);
6240
6241     NETDATA._loadjQuery(function() {
6242         NETDATA.loadRequiredJs(0, function() {
6243             if(typeof $().emulateTransitionEnd !== 'function') {
6244                 // bootstrap is not available
6245                 NETDATA.options.current.show_help = false;
6246             }
6247
6248             if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6249                 if(NETDATA.options.debug.main_loop === true)
6250                     console.log('starting chart refresh thread');
6251
6252                 NETDATA.start();
6253             }
6254         });
6255     });
6256
6257     // window.NETDATA = NETDATA;
6258 // })(window, document);