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