]> arthur.barton.de Git - netdata.git/blob - web/dashboard.js
dygraphs back to version dd74404 - the new version broke IE 11 support
[netdata.git] / web / dashboard.js
1 // You can set the following variables before loading this script:
2 //
3 // var netdataNoDygraphs = true;        // do not use dygraph
4 // var netdataNoSparklines = true;      // do not use sparkline
5 // var netdataNoPeitys = true;          // do not use peity
6 // var netdataNoGoogleCharts = true;    // do not use google
7 // var netdataNoMorris = true;          // do not use morris
8 // var netdataNoEasyPieChart = true;    // do not use easy pie chart
9 // var netdataNoGauge = true;           // do not use gauge.js
10 // var netdataNoD3 = true;              // do not use D3
11 // var netdataNoC3 = true;              // do not use C3
12 // var netdataNoBootstrap = true;       // do not load bootstrap
13 // var netdataDontStart = true;         // do not start the thread to process the charts
14 // var netdataErrorCallback = null;     // Callback function that will be invoked upon error
15 // var netdataRegistry = true;          // Update the registry (default disabled)
16 // var netdataRegistryCallback = null;  // Callback function that will be invoked with one param,
17 //                                         the URLs from the registry
18 // var netdataShowHelp = false;         // enable/disable help (default enabled)
19 // var netdataShowAlarms = true;        // enable/disable alarms checks and notifications (default disabled)
20 //
21 // var netdataRegistryAfterMs = 1500    // the time to consult to registry on startup
22 //
23 // var netdataCallback = null;          // a function to call when netdata is ready
24 //                                      // netdata will be running while this is called (call NETDATA.pause to stop it)
25 // var netdataPrepCallback = null;      // a callback to be called before netdata does anything else
26 //
27 // You can also set the default netdata server, using the following.
28 // When this variable is not set, we assume the page is hosted on your
29 // netdata server already.
30 // var netdataServer = "http://yourhost:19999"; // set your NetData server
31
32 //(function(window, document, undefined) {
33
34     // ------------------------------------------------------------------------
35     // compatibility fixes
36
37     // fix IE issue with console
38     if(!window.console) { window.console = { log: function(){} }; }
39
40     // if string.endsWith is not defined, define it
41     if(typeof String.prototype.endsWith !== 'function') {
42         String.prototype.endsWith = function(s) {
43             if(s.length > this.length) return false;
44             return this.slice(-s.length) === s;
45         };
46     }
47
48     // if string.startsWith is not defined, define it
49     if(typeof String.prototype.startsWith !== 'function') {
50         String.prototype.startsWith = function(s) {
51             if(s.length > this.length) return false;
52             return this.slice(s.length) === s;
53         };
54     }
55
56     // global namespace
57     var NETDATA = window.NETDATA || {};
58
59     NETDATA.name2id = function(s) {
60         return s
61             .replace(/ /g, '_')
62             .replace(/\(/g, '_')
63             .replace(/\)/g, '_')
64             .replace(/\./g, '_')
65             .replace(/\//g, '_');
66     };
67
68     // ----------------------------------------------------------------------------------------------------------------
69     // Detect the netdata server
70
71     // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
72     // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
73     NETDATA._scriptSource = function() {
74         var script = null;
75
76         if(typeof document.currentScript !== 'undefined') {
77             script = document.currentScript;
78         }
79         else {
80             var all_scripts = document.getElementsByTagName('script');
81             script = all_scripts[all_scripts.length - 1];
82         }
83
84         if (typeof script.getAttribute.length !== 'undefined')
85             script = script.src;
86         else
87             script = script.getAttribute('src', -1);
88
89         return script;
90     };
91
92     if(typeof netdataServer !== 'undefined')
93         NETDATA.serverDefault = netdataServer;
94     else {
95         var s = NETDATA._scriptSource();
96         if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
97         else {
98             console.log('WARNING: Cannot detect the URL of the netdata server.');
99             NETDATA.serverDefault = null;
100         }
101     }
102
103     if(NETDATA.serverDefault === null)
104         NETDATA.serverDefault = '';
105     else if(NETDATA.serverDefault.slice(-1) !== '/')
106         NETDATA.serverDefault += '/';
107
108     // default URLs for all the external files we need
109     // make them RELATIVE so that the whole thing can also be
110     // installed under a web server
111     NETDATA.jQuery              = NETDATA.serverDefault + 'lib/jquery-2.2.4.min.js';
112     NETDATA.peity_js            = NETDATA.serverDefault + 'lib/jquery.peity-3.2.0.min.js';
113     NETDATA.sparkline_js        = NETDATA.serverDefault + 'lib/jquery.sparkline-2.1.2.min.js';
114     NETDATA.easypiechart_js     = NETDATA.serverDefault + 'lib/jquery.easypiechart-97b5824.min.js';
115     NETDATA.gauge_js            = NETDATA.serverDefault + 'lib/gauge-d5260c3.min.js';
116     NETDATA.dygraph_js          = NETDATA.serverDefault + 'lib/dygraph-combined-dd74404.js';
117     NETDATA.dygraph_smooth_js   = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter-dd74404.js';
118     NETDATA.raphael_js          = NETDATA.serverDefault + 'lib/raphael-2.2.4-min.js';
119     NETDATA.c3_js               = NETDATA.serverDefault + 'lib/c3-0.4.11.min.js';
120     NETDATA.c3_css              = NETDATA.serverDefault + 'css/c3-0.4.11.min.css';
121     NETDATA.d3_js               = NETDATA.serverDefault + 'lib/d3-3.5.17.min.js';
122     NETDATA.morris_js           = NETDATA.serverDefault + 'lib/morris-0.5.1.min.js';
123     NETDATA.morris_css          = NETDATA.serverDefault + 'css/morris-0.5.1.css';
124     NETDATA.google_js           = 'https://www.google.com/jsapi';
125
126     NETDATA.themes = {
127         white: {
128             bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-3.3.7.min.css',
129             dashboard_css: NETDATA.serverDefault + 'dashboard.css?v20161002-1',
130             background: '#FFFFFF',
131             foreground: '#000000',
132             grid: '#DDDDDD',
133             axis: '#CCCCCC',
134             colors: [   '#3366CC', '#DC3912',   '#109618', '#FF9900',   '#990099', '#DD4477',
135                         '#3B3EAC', '#66AA00',   '#0099C6', '#B82E2E',   '#AAAA11', '#5574A6',
136                         '#994499', '#22AA99',   '#6633CC', '#E67300',   '#316395', '#8B0707',
137                         '#329262', '#3B3EAC' ],
138             easypiechart_track: '#f0f0f0',
139             easypiechart_scale: '#dfe0e0',
140             gauge_pointer: '#C0C0C0',
141             gauge_stroke: '#F0F0F0',
142             gauge_gradient: false
143         },
144         slate: {
145             bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.slate.min.css?v20161002-1',
146             dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css?v20161002-1',
147             background: '#272b30',
148             foreground: '#C8C8C8',
149             grid: '#35393e',
150             axis: '#35393e',
151 /*          colors: [   '#55bb33', '#ff2222',   '#0099C6', '#faa11b',   '#adbce0', '#DDDD00',
152                         '#4178ba', '#f58122',   '#a5cc39', '#f58667',   '#f5ef89', '#cf93c0',
153                         '#a5d18a', '#b8539d',   '#3954a3', '#c8a9cf',   '#c7de8a', '#fad20a',
154                         '#a6a479', '#a66da8' ],
155 */
156             colors: [   '#66AA00', '#FE3912',   '#3366CC', '#D66300',   '#0099C6', '#DDDD00',
157                         '#5054e6', '#EE9911',   '#BB44CC', '#e45757',   '#ef0aef', '#CC7700',
158                         '#22AA99', '#109618',   '#905bfd', '#f54882',   '#4381bf', '#ff3737',
159                         '#329262', '#3B3EFF' ],
160             easypiechart_track: '#373b40',
161             easypiechart_scale: '#373b40',
162             gauge_pointer: '#474b50',
163             gauge_stroke: '#373b40',
164             gauge_gradient: false
165         }
166     };
167
168     if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
169         NETDATA.themes.current = NETDATA.themes[netdataTheme];
170     else
171         NETDATA.themes.current = NETDATA.themes.white;
172
173     NETDATA.colors = NETDATA.themes.current.colors;
174
175     // these are the colors Google Charts are using
176     // we have them here to attempt emulate their look and feel on the other chart libraries
177     // http://there4.io/2012/05/02/google-chart-color-list/
178     //NETDATA.colors        = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
179     //                      '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
180     //                      '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
181
182     // an alternative set
183     // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
184     //                         (blue)     (red)      (orange)   (green)    (pink)     (brown)    (purple)   (yellow)   (gray)
185     //NETDATA.colors        = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
186
187     if(typeof netdataShowHelp === 'undefined')
188         netdataShowHelp = true;
189
190     if(typeof netdataShowAlarms === 'undefined')
191         netdataShowAlarms = false;
192
193     if(typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0)
194         netdataRegistryAfterMs = 1500;
195
196     if(typeof netdataRegistry === 'undefined') {
197         // backward compatibility
198         if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false)
199             netdataRegistry = true;
200         else
201             netdataRegistry = false;
202     }
203     if(netdataRegistry === false && typeof netdataRegistryCallback === 'function')
204         netdataRegistry = true;
205
206     // ----------------------------------------------------------------------------------------------------------------
207     // the defaults for all charts
208
209     // if the user does not specify any of these, the following will be used
210
211     NETDATA.chartDefaults = {
212         host: NETDATA.serverDefault,    // the server to get data from
213         width: '100%',                  // the chart width - can be null
214         height: '100%',                 // the chart height - can be null
215         min_width: null,                // the chart minimum width - can be null
216         library: 'dygraph',             // the graphing library to use
217         method: 'average',              // the grouping method
218         before: 0,                      // panning
219         after: -600,                    // panning
220         pixels_per_point: 1,            // the detail of the chart
221         fill_luminance: 0.8             // luminance of colors in solit areas
222     };
223
224     // ----------------------------------------------------------------------------------------------------------------
225     // global options
226
227     NETDATA.options = {
228         pauseCallback: null,            // a callback when we are really paused
229
230         pause: false,                   // when enabled we don't auto-refresh the charts
231
232         targets: null,                  // an array of all the state objects that are
233                                         // currently active (independently of their
234                                         // viewport visibility)
235
236         updated_dom: true,              // when true, the DOM has been updated with
237                                         // new elements we have to check.
238
239         auto_refresher_fast_weight: 0,  // this is the current time in ms, spent
240                                         // rendering charts continiously.
241                                         // used with .current.fast_render_timeframe
242
243         page_is_visible: true,          // when true, this page is visible
244
245         auto_refresher_stop_until: 0,   // timestamp in ms - used internaly, to stop the
246                                         // auto-refresher for some time (when a chart is
247                                         // performing pan or zoom, we need to stop refreshing
248                                         // all other charts, to have the maximum speed for
249                                         // rendering the chart that is panned or zoomed).
250                                         // Used with .current.global_pan_sync_time
251
252         last_resized: new Date().getTime(), // the timestamp of the last resize request
253
254         last_page_scroll: 0,            // the timestamp the last time the page was scrolled
255
256         // the current profile
257         // we may have many...
258         current: {
259             pixels_per_point: 1,        // the minimum pixels per point for all charts
260                                         // increase this to speed javascript up
261                                         // each chart library has its own limit too
262                                         // the max of this and the chart library is used
263                                         // the final is calculated every time, so a change
264                                         // here will have immediate effect on the next chart
265                                         // update
266
267             idle_between_charts: 100,   // ms - how much time to wait between chart updates
268
269             fast_render_timeframe: 200, // ms - render continously until this time of continious
270                                         // rendering has been reached
271                                         // this setting is used to make it render e.g. 10
272                                         // charts at once, sleep idle_between_charts time
273                                         // and continue for another 10 charts.
274
275             idle_between_loops: 500,    // ms - if all charts have been updated, wait this
276                                         // time before starting again.
277
278             idle_parallel_loops: 100,   // ms - the time between parallel refresher updates
279
280             idle_lost_focus: 500,       // ms - when the window does not have focus, check
281                                         // if focus has been regained, every this time
282
283             global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
284                                         // autorefreshing of charts is paused for this amount
285                                         // of time
286
287             sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
288                                         // of time before setting up synchronized selections
289                                         // on hover.
290
291             sync_selection: true,       // enable or disable selection sync
292
293             pan_and_zoom_delay: 50,     // when panning or zooming, how ofter to update the chart
294
295             sync_pan_and_zoom: true,    // enable or disable pan and zoom sync
296
297             pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
298
299             update_only_visible: true,  // enable or disable visibility management
300
301             parallel_refresher: true,   // enable parallel refresh of charts
302
303             concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
304
305             destroy_on_hide: false,     // destroy charts when they are not visible
306
307             show_help: netdataShowHelp, // when enabled the charts will show some help
308             show_help_delay_show_ms: 500,
309             show_help_delay_hide_ms: 0,
310
311             eliminate_zero_dimensions: true, // do not show dimensions with just zeros
312
313             stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
314             stop_updates_while_resizing: 1000,  // ms - time to stop auto-refreshes while resizing the charts
315
316             double_click_speed: 500,    // ms - time between clicks / taps to detect double click/tap
317
318             smooth_plot: true,          // enable smooth plot, where possible
319
320             charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
321
322             color_fill_opacity_line: 1.0,
323             color_fill_opacity_area: 0.2,
324             color_fill_opacity_stacked: 0.8,
325
326             pan_and_zoom_factor: 0.25,      // the increment when panning and zooming with the toolbox
327             pan_and_zoom_factor_multiplier_control: 2.0,
328             pan_and_zoom_factor_multiplier_shift: 3.0,
329             pan_and_zoom_factor_multiplier_alt: 4.0,
330
331             abort_ajax_on_scroll: false,            // kill pending ajax page scroll
332             async_on_scroll: false,                 // sync/async onscroll handler
333             onscroll_worker_duration_threshold: 30, // time in ms, to consider slow the onscroll handler
334
335             setOptionCallback: function() { ; }
336         },
337
338         debug: {
339             show_boxes:         false,
340             main_loop:          false,
341             focus:              false,
342             visibility:         false,
343             chart_data_url:     false,
344             chart_errors:       false, // FIXME
345             chart_timing:       false,
346             chart_calls:        false,
347             libraries:          false,
348             dygraph:            false
349         }
350     };
351
352     NETDATA.statistics = {
353         refreshes_total: 0,
354         refreshes_active: 0,
355         refreshes_active_max: 0
356     };
357
358
359     // ----------------------------------------------------------------------------------------------------------------
360     // local storage options
361
362     NETDATA.localStorage = {
363         default: {},
364         current: {},
365         callback: {} // only used for resetting back to defaults
366     };
367
368     NETDATA.localStorageGet = function(key, def, callback) {
369         var ret = def;
370
371         if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
372             NETDATA.localStorage.default[key.toString()] = def;
373             NETDATA.localStorage.callback[key.toString()] = callback;
374         }
375
376         if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
377             try {
378                 // console.log('localStorage: loading "' + key.toString() + '"');
379                 ret = localStorage.getItem(key.toString());
380                 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
381                 if(ret === null || ret === 'undefined') {
382                     // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
383                     localStorage.setItem(key.toString(), JSON.stringify(def));
384                     ret = def;
385                 }
386                 else {
387                     // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
388                     ret = JSON.parse(ret);
389                     // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
390                 }
391             }
392             catch(error) {
393                 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
394                 ret = def;
395             }
396         }
397
398         if(typeof ret === 'undefined' || ret === 'undefined') {
399             console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
400             ret = def;
401         }
402
403         NETDATA.localStorage.current[key.toString()] = ret;
404         return ret;
405     };
406
407     NETDATA.localStorageSet = function(key, value, callback) {
408         if(typeof value === 'undefined' || value === 'undefined') {
409             console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
410         }
411
412         if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
413             NETDATA.localStorage.default[key.toString()] = value;
414             NETDATA.localStorage.current[key.toString()] = value;
415             NETDATA.localStorage.callback[key.toString()] = callback;
416         }
417
418         if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
419             // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
420             try {
421                 localStorage.setItem(key.toString(), JSON.stringify(value));
422             }
423             catch(e) {
424                 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
425             }
426         }
427
428         NETDATA.localStorage.current[key.toString()] = value;
429         return value;
430     };
431
432     NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
433         for(var i in obj) {
434             if(typeof obj[i] === 'object') {
435                 //console.log('object ' + prefix + '.' + i.toString());
436                 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
437                 continue;
438             }
439
440             obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
441         }
442     };
443
444     NETDATA.setOption = function(key, value) {
445         if(key.toString() === 'setOptionCallback') {
446             if(typeof NETDATA.options.current.setOptionCallback === 'function') {
447                 NETDATA.options.current[key.toString()] = value;
448                 NETDATA.options.current.setOptionCallback();
449             }
450         }
451         else if(NETDATA.options.current[key.toString()] !== value) {
452             var name = 'options.' + key.toString();
453
454             if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
455                 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
456
457             //console.log(NETDATA.localStorage);
458             //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
459             //console.log(NETDATA.options);
460             NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
461
462             if(typeof NETDATA.options.current.setOptionCallback === 'function')
463                 NETDATA.options.current.setOptionCallback();
464         }
465
466         return true;
467     };
468
469     NETDATA.getOption = function(key) {
470         return NETDATA.options.current[key.toString()];
471     };
472
473     // read settings from local storage
474     NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
475
476     // always start with this option enabled.
477     NETDATA.setOption('stop_updates_when_focus_is_lost', true);
478
479     NETDATA.resetOptions = function() {
480         for(var i in NETDATA.localStorage.default) {
481             var a = i.split('.');
482
483             if(a[0] === 'options') {
484                 if(a[1] === 'setOptionCallback') continue;
485                 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
486                 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
487
488                 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
489             }
490             else if(a[0] === 'chart_heights') {
491                 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
492                     NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
493                 }
494             }
495         }
496     }
497
498     // ----------------------------------------------------------------------------------------------------------------
499
500     if(NETDATA.options.debug.main_loop === true)
501         console.log('welcome to NETDATA');
502
503     NETDATA.onresize = function() {
504         NETDATA.options.last_resized = new Date().getTime();
505         NETDATA.onscroll();
506     };
507
508     NETDATA.onscroll_updater_count = 0;
509     NETDATA.onscroll_updater_running = false;
510     NETDATA.onscroll_updater_last_run = 0;
511     NETDATA.onscroll_updater_watchdog = null;
512     NETDATA.onscroll_updater_max_duration = 0;
513     NETDATA.onscroll_updater_above_threshold_count = 0;
514     NETDATA.onscroll_updater = function() {
515         NETDATA.onscroll_updater_running = true;
516         NETDATA.onscroll_updater_count++;
517         var start = new Date().getTime();
518
519         var targets = NETDATA.options.targets;
520         var len = targets.length;
521
522         // when the user scrolls he sees that we have
523         // hidden all the not-visible charts
524         // using this little function we try to switch
525         // the charts back to visible quickly
526
527
528         if(NETDATA.options.abort_ajax_on_scroll === true) {
529             // we have to cancel pending requests too
530
531             while (len--) {
532                 if (targets[len]._updating === true) {
533                     if (typeof targets[len].xhr !== 'undefined') {
534                         targets[len].xhr.abort();
535                         targets[len].running = false;
536                         targets[len]._updating = false;
537                     }
538                     targets[len].isVisible();
539                 }
540             }
541         }
542         else {
543             // just find which chart is visible
544
545             while (len--)
546                 targets[len].isVisible();
547         }
548
549         var end = new Date().getTime();
550         // console.log('scroll No ' + NETDATA.onscroll_updater_count + ' calculation took ' + (end - start).toString() + ' ms');
551
552         if(NETDATA.options.current.async_on_scroll === false) {
553             var dt = end - start;
554             if(dt > NETDATA.onscroll_updater_max_duration) {
555                 // console.log('max onscroll event handler duration increased to ' + dt);
556                 NETDATA.onscroll_updater_max_duration = dt;
557             }
558
559             if(dt > NETDATA.options.current.onscroll_worker_duration_threshold) {
560                 // console.log('slow: ' + dt);
561                 NETDATA.onscroll_updater_above_threshold_count++;
562
563                 if(NETDATA.onscroll_updater_above_threshold_count > 2 && NETDATA.onscroll_updater_above_threshold_count * 100 / NETDATA.onscroll_updater_count > 2) {
564                     NETDATA.setOption('async_on_scroll', true);
565                     console.log('NETDATA: your browser is slow - enabling asynchronous onscroll event handler.');
566                 }
567             }
568         }
569
570         NETDATA.onscroll_updater_last_run = start;
571         NETDATA.onscroll_updater_running = false;
572     };
573
574     NETDATA.onscroll = function() {
575         // console.log('onscroll');
576
577         NETDATA.options.last_page_scroll = new Date().getTime();
578         NETDATA.options.auto_refresher_stop_until = 0;
579
580         if(NETDATA.options.targets === null) return;
581
582         if(NETDATA.options.current.async_on_scroll === true) {
583             // async
584             if(NETDATA.onscroll_updater_running === false) {
585                 NETDATA.onscroll_updater_running = true;
586                 setTimeout(NETDATA.onscroll_updater, 0);
587             }
588             else {
589                 if(NETDATA.onscroll_updater_watchdog !== null)
590                     clearTimeout(NETDATA.onscroll_updater_watchdog);
591
592                 NETDATA.onscroll_updater_watchdog = setTimeout(function() {
593                     if(NETDATA.onscroll_updater_running === false && NETDATA.options.last_page_scroll > NETDATA.onscroll_updater_last_run) {
594                         // console.log('watchdog');
595                         NETDATA.onscroll_updater();
596                     }
597
598                     NETDATA.onscroll_updater_watchdog = null;
599                 }, 200);
600             }
601         }
602         else {
603             // sync
604             NETDATA.onscroll_updater();
605         }
606     };
607
608     window.onresize = NETDATA.onresize;
609     window.onscroll = NETDATA.onscroll;
610
611     // ----------------------------------------------------------------------------------------------------------------
612     // Error Handling
613
614     NETDATA.errorCodes = {
615         100: { message: "Cannot load chart library", alert: true },
616         101: { message: "Cannot load jQuery", alert: true },
617         402: { message: "Chart library not found", alert: false },
618         403: { message: "Chart library not enabled/is failed", alert: false },
619         404: { message: "Chart not found", alert: false },
620         405: { message: "Cannot download charts index from server", alert: true },
621         406: { message: "Invalid charts index downloaded from server", alert: true },
622         407: { message: "Cannot HELLO netdata server", alert: false },
623         408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
624         409: { message: "Cannot ACCESS netdata registry", alert: false },
625         410: { message: "Netdata registry ACCESS failed", alert: false },
626         411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
627         412: { message: "Netdata registry DELETE failed", alert: false },
628         413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
629         414: { message: "Netdata registry SWITCH failed", alert: false },
630         415: { message: "Netdata alarms download failed", alert: false },
631         416: { message: "Netdata alarms log download failed", alert: false },
632         417: { message: "Netdata registry server send invalid response to SEARCH ", alert: false },
633         418: { message: "Netdata registry SEARCH failed", alert: false }
634     };
635     NETDATA.errorLast = {
636         code: 0,
637         message: "",
638         datetime: 0
639     };
640
641     NETDATA.error = function(code, msg) {
642         NETDATA.errorLast.code = code;
643         NETDATA.errorLast.message = msg;
644         NETDATA.errorLast.datetime = new Date().getTime();
645
646         console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
647
648         var ret = true;
649         if(typeof netdataErrorCallback === 'function') {
650            ret = netdataErrorCallback('system', code, msg);
651         }
652
653         if(ret && NETDATA.errorCodes[code].alert)
654             alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
655     };
656
657     NETDATA.errorReset = function() {
658         NETDATA.errorLast.code = 0;
659         NETDATA.errorLast.message = "You are doing fine!";
660         NETDATA.errorLast.datetime = 0;
661     };
662
663     // ----------------------------------------------------------------------------------------------------------------
664     // Chart Registry
665
666     // When multiple charts need the same chart, we avoid downloading it
667     // multiple times (and having it in browser memory multiple time)
668     // by using this registry.
669
670     // Every time we download a chart definition, we save it here with .add()
671     // Then we try to get it back with .get(). If that fails, we download it.
672
673     NETDATA.chartRegistry = {
674         charts: {},
675
676         fixid: function(id) {
677             return id.replace(/:/g, "_").replace(/\//g, "_");
678         },
679
680         add: function(host, id, data) {
681             host = this.fixid(host);
682             id   = this.fixid(id);
683
684             if(typeof this.charts[host] === 'undefined')
685                 this.charts[host] = {};
686
687             //console.log('added ' + host + '/' + id);
688             this.charts[host][id] = data;
689         },
690
691         get: function(host, id) {
692             host = this.fixid(host);
693             id   = this.fixid(id);
694
695             if(typeof this.charts[host] === 'undefined')
696                 return null;
697
698             if(typeof this.charts[host][id] === 'undefined')
699                 return null;
700
701             //console.log('cached ' + host + '/' + id);
702             return this.charts[host][id];
703         },
704
705         downloadAll: function(host, callback) {
706             while(host.slice(-1) === '/')
707                 host = host.substring(0, host.length - 1);
708
709             var self = this;
710
711             $.ajax({
712                 url: host + '/api/v1/charts',
713                 async: true,
714                 cache: false,
715                 xhrFields: { withCredentials: true } // required for the cookie
716             })
717             .done(function(data) {
718                 if(data !== null) {
719                     var h = NETDATA.chartRegistry.fixid(host);
720                     self.charts[h] = data.charts;
721                 }
722                 else NETDATA.error(406, host + '/api/v1/charts');
723
724                 if(typeof callback === 'function')
725                     callback(data);
726             })
727             .fail(function() {
728                 NETDATA.error(405, host + '/api/v1/charts');
729
730                 if(typeof callback === 'function')
731                     callback(null);
732             });
733         }
734     };
735
736     // ----------------------------------------------------------------------------------------------------------------
737     // Global Pan and Zoom on charts
738
739     // Using this structure are synchronize all the charts, so that
740     // when you pan or zoom one, all others are automatically refreshed
741     // to the same timespan.
742
743     NETDATA.globalPanAndZoom = {
744         seq: 0,                 // timestamp ms
745                                 // every time a chart is panned or zoomed
746                                 // we set the timestamp here
747                                 // then we use it as a sequence number
748                                 // to find if other charts are syncronized
749                                 // to this timerange
750
751         master: null,           // the master chart (state), to which all others
752                                 // are synchronized
753
754         force_before_ms: null,  // the timespan to sync all other charts
755         force_after_ms: null,
756
757         callback: null,
758
759         // set a new master
760         setMaster: function(state, after, before) {
761             if(NETDATA.options.current.sync_pan_and_zoom === false)
762                 return;
763
764             if(this.master !== null && this.master !== state)
765                 this.master.resetChart(true, true);
766
767             var now = new Date().getTime();
768             this.master = state;
769             this.seq = now;
770             this.force_after_ms = after;
771             this.force_before_ms = before;
772             NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
773
774             if(typeof this.callback === 'function')
775                 this.callback(true, after, before);
776         },
777
778         // clear the master
779         clearMaster: function() {
780             if(this.master !== null) {
781                 var st = this.master;
782                 this.master = null;
783                 st.resetChart();
784             }
785
786             this.master = null;
787             this.seq = 0;
788             this.force_after_ms = null;
789             this.force_before_ms = null;
790             NETDATA.options.auto_refresher_stop_until = 0;
791
792             if(typeof this.callback === 'function')
793                 this.callback(false, 0, 0);
794         },
795
796         // is the given state the master of the global
797         // pan and zoom sync?
798         isMaster: function(state) {
799             if(this.master === state) return true;
800             return false;
801         },
802
803         // are we currently have a global pan and zoom sync?
804         isActive: function() {
805             if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
806             return false;
807         },
808
809         // check if a chart, other than the master
810         // needs to be refreshed, due to the global pan and zoom
811         shouldBeAutoRefreshed: function(state) {
812             if(this.master === null || this.seq === 0)
813                 return false;
814
815             //if(state.needsRecreation())
816             //  return true;
817
818             if(state.tm.pan_and_zoom_seq === this.seq)
819                 return false;
820
821             return true;
822         }
823     };
824
825     // ----------------------------------------------------------------------------------------------------------------
826     // dimensions selection
827
828     // FIXME
829     // move color assignment to dimensions, here
830
831     dimensionStatus = function(parent, label, name_div, value_div, color) {
832         this.enabled = false;
833         this.parent = parent;
834         this.label = label;
835         this.name_div = null;
836         this.value_div = null;
837         this.color = NETDATA.themes.current.foreground;
838
839         if(parent.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             gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4007             gridLineWidth: self.data('dygraph-gridlinewidth') || 0.4,
4008             gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4009
4010             maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4011             sigFigs: self.data('dygraph-sigfigs') || null,
4012             digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4013             valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4014
4015             highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4016             highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4017             highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4018
4019             pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4020             visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4021             axes: {
4022                 x: {
4023                     pixelsPerLabel: 50,
4024                     ticker: Dygraph.dateTicker,
4025                     axisLabelFormatter: function (d, gran) {
4026                         return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4027                     },
4028                     valueFormatter: function (ms) {
4029                         var d = new Date(ms);
4030                         return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4031                         // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4032                     }
4033                 },
4034                 y: {
4035                     pixelsPerLabel: 15,
4036                     valueFormatter: function (x) {
4037                         // we format legends with the state object
4038                         // no need to do anything here
4039                         // return (Math.round(x*100) / 100).toLocaleString();
4040                         // return state.legendFormatValue(x);
4041                         return x;
4042                     }
4043                 }
4044             },
4045             legendFormatter: function(data) {
4046                 var elements = state.element_legend_childs;
4047
4048                 // if the hidden div is not there
4049                 // we are not managing the legend
4050                 if(elements.hidden === null) return;
4051
4052                 if (typeof data.x !== 'undefined') {
4053                     state.legendSetDate(data.x);
4054                     var i = data.series.length;
4055                     while(i--) {
4056                         var series = data.series[i];
4057                         if(!series.isVisible) continue;
4058                         state.legendSetLabelValue(series.label, series.y);
4059                     }
4060                 }
4061
4062                 return '';
4063             },
4064             drawCallback: function(dygraph, is_initial) {
4065                 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4066                     state.dygraph_user_action = false;
4067
4068                     var x_range = dygraph.xAxisRange();
4069                     var after = Math.round(x_range[0]);
4070                     var before = Math.round(x_range[1]);
4071
4072                     if(NETDATA.options.debug.dygraph === true)
4073                         state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4074
4075                     if(before <= state.netdata_last && after >= state.netdata_first)
4076                         state.updateChartPanOrZoom(after, before);
4077                 }
4078             },
4079             zoomCallback: function(minDate, maxDate, yRanges) {
4080                 if(NETDATA.options.debug.dygraph === true)
4081                     state.log('dygraphZoomCallback()');
4082
4083                 state.globalSelectionSyncStop();
4084                 state.globalSelectionSyncDelay();
4085                 state.setMode('zoom');
4086
4087                 // refresh it to the greatest possible zoom level
4088                 state.dygraph_user_action = true;
4089                 state.dygraph_force_zoom = true;
4090                 state.updateChartPanOrZoom(minDate, maxDate);
4091             },
4092             highlightCallback: function(event, x, points, row, seriesName) {
4093                 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4094                     state.log('dygraphHighlightCallback()');
4095
4096                 state.pauseChart();
4097
4098                 // there is a bug in dygraph when the chart is zoomed enough
4099                 // the time it thinks is selected is wrong
4100                 // here we calculate the time t based on the row number selected
4101                 // which is ok
4102                 var t = state.data_after + row * state.data_update_every;
4103                 // 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);
4104
4105                 state.globalSelectionSync(x);
4106
4107                 // fix legend zIndex using the internal structures of dygraph legend module
4108                 // this works, but it is a hack!
4109                 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4110             },
4111             unhighlightCallback: function(event) {
4112                 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4113                     state.log('dygraphUnhighlightCallback()');
4114
4115                 state.unpauseChart();
4116                 state.globalSelectionSyncStop();
4117             },
4118             interactionModel : {
4119                 mousedown: function(event, dygraph, context) {
4120                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4121                         state.log('interactionModel.mousedown()');
4122
4123                     state.dygraph_user_action = true;
4124                     state.globalSelectionSyncStop();
4125
4126                     if(NETDATA.options.debug.dygraph === true)
4127                         state.log('dygraphMouseDown()');
4128
4129                     // Right-click should not initiate a zoom.
4130                     if(event.button && event.button === 2) return;
4131
4132                     context.initializeMouseDown(event, dygraph, context);
4133
4134                     if(event.button && event.button === 1) {
4135                         if (event.altKey || event.shiftKey) {
4136                             state.setMode('pan');
4137                             state.globalSelectionSyncDelay();
4138                             Dygraph.startPan(event, dygraph, context);
4139                         }
4140                         else {
4141                             state.setMode('zoom');
4142                             state.globalSelectionSyncDelay();
4143                             Dygraph.startZoom(event, dygraph, context);
4144                         }
4145                     }
4146                     else {
4147                         if (event.altKey || event.shiftKey) {
4148                             state.setMode('zoom');
4149                             state.globalSelectionSyncDelay();
4150                             Dygraph.startZoom(event, dygraph, context);
4151                         }
4152                         else {
4153                             state.setMode('pan');
4154                             state.globalSelectionSyncDelay();
4155                             Dygraph.startPan(event, dygraph, context);
4156                         }
4157                     }
4158                 },
4159                 mousemove: function(event, dygraph, context) {
4160                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4161                         state.log('interactionModel.mousemove()');
4162
4163                     if(context.isPanning) {
4164                         state.dygraph_user_action = true;
4165                         state.globalSelectionSyncStop();
4166                         state.globalSelectionSyncDelay();
4167                         state.setMode('pan');
4168                         Dygraph.movePan(event, dygraph, context);
4169                     }
4170                     else if(context.isZooming) {
4171                         state.dygraph_user_action = true;
4172                         state.globalSelectionSyncStop();
4173                         state.globalSelectionSyncDelay();
4174                         state.setMode('zoom');
4175                         Dygraph.moveZoom(event, dygraph, context);
4176                     }
4177                 },
4178                 mouseup: function(event, dygraph, context) {
4179                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4180                         state.log('interactionModel.mouseup()');
4181
4182                     if (context.isPanning) {
4183                         state.dygraph_user_action = true;
4184                         state.globalSelectionSyncDelay();
4185                         Dygraph.endPan(event, dygraph, context);
4186                     }
4187                     else if (context.isZooming) {
4188                         state.dygraph_user_action = true;
4189                         state.globalSelectionSyncDelay();
4190                         Dygraph.endZoom(event, dygraph, context);
4191                     }
4192                 },
4193                 click: function(event, dygraph, context) {
4194                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4195                         state.log('interactionModel.click()');
4196
4197                     event.preventDefault();
4198                 },
4199                 dblclick: function(event, dygraph, context) {
4200                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4201                         state.log('interactionModel.dblclick()');
4202                     NETDATA.resetAllCharts(state);
4203                 },
4204                 mousewheel: function(event, dygraph, context) {
4205                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4206                         state.log('interactionModel.mousewheel()');
4207
4208                     // Take the offset of a mouse event on the dygraph canvas and
4209                     // convert it to a pair of percentages from the bottom left.
4210                     // (Not top left, bottom is where the lower value is.)
4211                     function offsetToPercentage(g, offsetX, offsetY) {
4212                         // This is calculating the pixel offset of the leftmost date.
4213                         var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4214                         var yar0 = g.yAxisRange(0);
4215
4216                         // This is calculating the pixel of the higest value. (Top pixel)
4217                         var yOffset = g.toDomCoords(null, yar0[1])[1];
4218
4219                         // x y w and h are relative to the corner of the drawing area,
4220                         // so that the upper corner of the drawing area is (0, 0).
4221                         var x = offsetX - xOffset;
4222                         var y = offsetY - yOffset;
4223
4224                         // This is computing the rightmost pixel, effectively defining the
4225                         // width.
4226                         var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4227
4228                         // This is computing the lowest pixel, effectively defining the height.
4229                         var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4230
4231                         // Percentage from the left.
4232                         var xPct = w === 0 ? 0 : (x / w);
4233                         // Percentage from the top.
4234                         var yPct = h === 0 ? 0 : (y / h);
4235
4236                         // The (1-) part below changes it from "% distance down from the top"
4237                         // to "% distance up from the bottom".
4238                         return [xPct, (1-yPct)];
4239                     }
4240
4241                     // Adjusts [x, y] toward each other by zoomInPercentage%
4242                     // Split it so the left/bottom axis gets xBias/yBias of that change and
4243                     // tight/top gets (1-xBias)/(1-yBias) of that change.
4244                     //
4245                     // If a bias is missing it splits it down the middle.
4246                     function zoomRange(g, zoomInPercentage, xBias, yBias) {
4247                         xBias = xBias || 0.5;
4248                         yBias = yBias || 0.5;
4249
4250                         function adjustAxis(axis, zoomInPercentage, bias) {
4251                             var delta = axis[1] - axis[0];
4252                             var increment = delta * zoomInPercentage;
4253                             var foo = [increment * bias, increment * (1-bias)];
4254
4255                             return [ axis[0] + foo[0], axis[1] - foo[1] ];
4256                         }
4257
4258                         var yAxes = g.yAxisRanges();
4259                         var newYAxes = [];
4260                         for (var i = 0; i < yAxes.length; i++) {
4261                             newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4262                         }
4263
4264                         return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4265                     }
4266
4267                     if(event.altKey || event.shiftKey) {
4268                         state.dygraph_user_action = true;
4269
4270                         state.globalSelectionSyncStop();
4271                         state.globalSelectionSyncDelay();
4272
4273                         // http://dygraphs.com/gallery/interaction-api.js
4274                         var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4275                         var percentage = normal / 50;
4276
4277                         if (!(event.offsetX && event.offsetY)){
4278                             event.offsetX = event.layerX - event.target.offsetLeft;
4279                             event.offsetY = event.layerY - event.target.offsetTop;
4280                         }
4281
4282                         var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4283                         var xPct = percentages[0];
4284                         var yPct = percentages[1];
4285
4286                         var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4287
4288                         var after = new_x_range[0];
4289                         var before = new_x_range[1];
4290
4291                         var first = state.netdata_first + state.data_update_every;
4292                         var last = state.netdata_last + state.data_update_every;
4293
4294                         if(before > last) {
4295                             after -= (before - last);
4296                             before = last;
4297                         }
4298                         if(after < first) {
4299                             after = first;
4300                         }
4301
4302                         state.setMode('zoom');
4303                         if(state.updateChartPanOrZoom(after, before) === true)
4304                             dygraph.updateOptions({ dateWindow: [ after, before ] });
4305
4306                         event.preventDefault();
4307                     }
4308                 },
4309                 touchstart: function(event, dygraph, context) {
4310                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4311                         state.log('interactionModel.touchstart()');
4312
4313                     state.dygraph_user_action = true;
4314                     state.setMode('zoom');
4315                     state.pauseChart();
4316
4317                     Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4318
4319                     // we overwrite the touch directions at the end, to overwrite
4320                     // the internal default of dygraphs
4321                     context.touchDirections = { x: true, y: false };
4322
4323                     state.dygraph_last_touch_start = new Date().getTime();
4324                     state.dygraph_last_touch_move = 0;
4325
4326                     if(typeof event.touches[0].pageX === 'number')
4327                         state.dygraph_last_touch_page_x = event.touches[0].pageX;
4328                     else
4329                         state.dygraph_last_touch_page_x = 0;
4330                 },
4331                 touchmove: function(event, dygraph, context) {
4332                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4333                         state.log('interactionModel.touchmove()');
4334
4335                     state.dygraph_user_action = true;
4336                     Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4337
4338                     state.dygraph_last_touch_move = new Date().getTime();
4339                 },
4340                 touchend: function(event, dygraph, context) {
4341                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4342                         state.log('interactionModel.touchend()');
4343
4344                     state.dygraph_user_action = true;
4345                     Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4346
4347                     // if it didn't move, it is a selection
4348                     if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4349                         // internal api of dygraphs
4350                         var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4351                         var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4352                         if(NETDATA.dygraphSetSelection(state, t) === true)
4353                             state.globalSelectionSync(t);
4354                     }
4355
4356                     // if it was double tap within double click time, reset the charts
4357                     var now = new Date().getTime();
4358                     if(typeof state.dygraph_last_touch_end !== 'undefined') {
4359                         if(state.dygraph_last_touch_move === 0) {
4360                             var dt = now - state.dygraph_last_touch_end;
4361                             if(dt <= NETDATA.options.current.double_click_speed)
4362                                 NETDATA.resetAllCharts(state);
4363                         }
4364                     }
4365
4366                     // remember the timestamp of the last touch end
4367                     state.dygraph_last_touch_end = now;
4368                 }
4369             }
4370         };
4371
4372         if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4373             state.dygraph_options.drawGrid = false;
4374             state.dygraph_options.drawAxis = false;
4375             state.dygraph_options.title = undefined;
4376             state.dygraph_options.ylabel = undefined;
4377             state.dygraph_options.yLabelWidth = 0;
4378             state.dygraph_options.labelsDivWidth = 120;
4379             state.dygraph_options.labelsDivStyles.width = '120px';
4380             state.dygraph_options.labelsSeparateLines = true;
4381             state.dygraph_options.rightGap = 0;
4382             state.dygraph_options.yRangePad = 1;
4383         }
4384
4385         if(smooth === true) {
4386             state.dygraph_smooth_eligible = true;
4387
4388             if(NETDATA.options.current.smooth_plot === true)
4389                 state.dygraph_options.plotter = smoothPlotter;
4390         }
4391         else state.dygraph_smooth_eligible = false;
4392
4393         state.dygraph_instance = new Dygraph(state.element_chart,
4394             data.result.data, state.dygraph_options);
4395
4396         state.dygraph_force_zoom = false;
4397         state.dygraph_user_action = false;
4398         state.dygraph_last_rendered = new Date().getTime();
4399         return true;
4400     };
4401
4402     // ----------------------------------------------------------------------------------------------------------------
4403     // morris
4404
4405     NETDATA.morrisInitialize = function(callback) {
4406         if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4407
4408             // morris requires raphael
4409             if(!NETDATA.chartLibraries.raphael.initialized) {
4410                 if(NETDATA.chartLibraries.raphael.enabled) {
4411                     NETDATA.raphaelInitialize(function() {
4412                         NETDATA.morrisInitialize(callback);
4413                     });
4414                 }
4415                 else {
4416                     NETDATA.chartLibraries.morris.enabled = false;
4417                     if(typeof callback === "function")
4418                         callback();
4419                 }
4420             }
4421             else {
4422                 NETDATA._loadCSS(NETDATA.morris_css);
4423
4424                 $.ajax({
4425                     url: NETDATA.morris_js,
4426                     cache: true,
4427                     dataType: "script",
4428                     xhrFields: { withCredentials: true } // required for the cookie
4429                 })
4430                 .done(function() {
4431                     NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4432                 })
4433                 .fail(function() {
4434                     NETDATA.chartLibraries.morris.enabled = false;
4435                     NETDATA.error(100, NETDATA.morris_js);
4436                 })
4437                 .always(function() {
4438                     if(typeof callback === "function")
4439                         callback();
4440                 });
4441             }
4442         }
4443         else {
4444             NETDATA.chartLibraries.morris.enabled = false;
4445             if(typeof callback === "function")
4446                 callback();
4447         }
4448     };
4449
4450     NETDATA.morrisChartUpdate = function(state, data) {
4451         state.morris_instance.setData(data.result.data);
4452         return true;
4453     };
4454
4455     NETDATA.morrisChartCreate = function(state, data) {
4456
4457         state.morris_options = {
4458                 element: state.element_chart.id,
4459                 data: data.result.data,
4460                 xkey: 'time',
4461                 ykeys: data.dimension_names,
4462                 labels: data.dimension_names,
4463                 lineWidth: 2,
4464                 pointSize: 3,
4465                 smooth: true,
4466                 hideHover: 'auto',
4467                 parseTime: true,
4468                 continuousLine: false,
4469                 behaveLikeLine: false
4470         };
4471
4472         if(state.chart.chart_type === 'line')
4473             state.morris_instance = new Morris.Line(state.morris_options);
4474
4475         else if(state.chart.chart_type === 'area') {
4476             state.morris_options.behaveLikeLine = true;
4477             state.morris_instance = new Morris.Area(state.morris_options);
4478         }
4479         else // stacked
4480             state.morris_instance = new Morris.Area(state.morris_options);
4481
4482         return true;
4483     };
4484
4485     // ----------------------------------------------------------------------------------------------------------------
4486     // raphael
4487
4488     NETDATA.raphaelInitialize = function(callback) {
4489         if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4490             $.ajax({
4491                 url: NETDATA.raphael_js,
4492                 cache: true,
4493                 dataType: "script",
4494                 xhrFields: { withCredentials: true } // required for the cookie
4495             })
4496             .done(function() {
4497                 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4498             })
4499             .fail(function() {
4500                 NETDATA.chartLibraries.raphael.enabled = false;
4501                 NETDATA.error(100, NETDATA.raphael_js);
4502             })
4503             .always(function() {
4504                 if(typeof callback === "function")
4505                     callback();
4506             });
4507         }
4508         else {
4509             NETDATA.chartLibraries.raphael.enabled = false;
4510             if(typeof callback === "function")
4511                 callback();
4512         }
4513     };
4514
4515     NETDATA.raphaelChartUpdate = function(state, data) {
4516         $(state.element_chart).raphael(data.result, {
4517             width: state.chartWidth(),
4518             height: state.chartHeight()
4519         });
4520
4521         return false;
4522     };
4523
4524     NETDATA.raphaelChartCreate = function(state, data) {
4525         $(state.element_chart).raphael(data.result, {
4526             width: state.chartWidth(),
4527             height: state.chartHeight()
4528         });
4529
4530         return false;
4531     };
4532
4533     // ----------------------------------------------------------------------------------------------------------------
4534     // C3
4535
4536     NETDATA.c3Initialize = function(callback) {
4537         if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4538
4539             // C3 requires D3
4540             if(!NETDATA.chartLibraries.d3.initialized) {
4541                 if(NETDATA.chartLibraries.d3.enabled) {
4542                     NETDATA.d3Initialize(function() {
4543                         NETDATA.c3Initialize(callback);
4544                     });
4545                 }
4546                 else {
4547                     NETDATA.chartLibraries.c3.enabled = false;
4548                     if(typeof callback === "function")
4549                         callback();
4550                 }
4551             }
4552             else {
4553                 NETDATA._loadCSS(NETDATA.c3_css);
4554
4555                 $.ajax({
4556                     url: NETDATA.c3_js,
4557                     cache: true,
4558                     dataType: "script",
4559                     xhrFields: { withCredentials: true } // required for the cookie
4560                 })
4561                 .done(function() {
4562                     NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4563                 })
4564                 .fail(function() {
4565                     NETDATA.chartLibraries.c3.enabled = false;
4566                     NETDATA.error(100, NETDATA.c3_js);
4567                 })
4568                 .always(function() {
4569                     if(typeof callback === "function")
4570                         callback();
4571                 });
4572             }
4573         }
4574         else {
4575             NETDATA.chartLibraries.c3.enabled = false;
4576             if(typeof callback === "function")
4577                 callback();
4578         }
4579     };
4580
4581     NETDATA.c3ChartUpdate = function(state, data) {
4582         state.c3_instance.destroy();
4583         return NETDATA.c3ChartCreate(state, data);
4584
4585         //state.c3_instance.load({
4586         //  rows: data.result,
4587         //  unload: true
4588         //});
4589
4590         //return true;
4591     };
4592
4593     NETDATA.c3ChartCreate = function(state, data) {
4594
4595         state.element_chart.id = 'c3-' + state.uuid;
4596         // console.log('id = ' + state.element_chart.id);
4597
4598         state.c3_instance = c3.generate({
4599             bindto: '#' + state.element_chart.id,
4600             size: {
4601                 width: state.chartWidth(),
4602                 height: state.chartHeight()
4603             },
4604             color: {
4605                 pattern: state.chartColors()
4606             },
4607             data: {
4608                 x: 'time',
4609                 rows: data.result,
4610                 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4611             },
4612             axis: {
4613                 x: {
4614                     type: 'timeseries',
4615                     tick: {
4616                         format: function(x) {
4617                             return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4618                         }
4619                     }
4620                 }
4621             },
4622             grid: {
4623                 x: {
4624                     show: true
4625                 },
4626                 y: {
4627                     show: true
4628                 }
4629             },
4630             point: {
4631                 show: false
4632             },
4633             line: {
4634                 connectNull: false
4635             },
4636             transition: {
4637                 duration: 0
4638             },
4639             interaction: {
4640                 enabled: true
4641             }
4642         });
4643
4644         // console.log(state.c3_instance);
4645
4646         return true;
4647     };
4648
4649     // ----------------------------------------------------------------------------------------------------------------
4650     // D3
4651
4652     NETDATA.d3Initialize = function(callback) {
4653         if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4654             $.ajax({
4655                 url: NETDATA.d3_js,
4656                 cache: true,
4657                 dataType: "script",
4658                 xhrFields: { withCredentials: true } // required for the cookie
4659             })
4660             .done(function() {
4661                 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4662             })
4663             .fail(function() {
4664                 NETDATA.chartLibraries.d3.enabled = false;
4665                 NETDATA.error(100, NETDATA.d3_js);
4666             })
4667             .always(function() {
4668                 if(typeof callback === "function")
4669                     callback();
4670             });
4671         }
4672         else {
4673             NETDATA.chartLibraries.d3.enabled = false;
4674             if(typeof callback === "function")
4675                 callback();
4676         }
4677     };
4678
4679     NETDATA.d3ChartUpdate = function(state, data) {
4680         return false;
4681     };
4682
4683     NETDATA.d3ChartCreate = function(state, data) {
4684         return false;
4685     };
4686
4687     // ----------------------------------------------------------------------------------------------------------------
4688     // google charts
4689
4690     NETDATA.googleInitialize = function(callback) {
4691         if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4692             $.ajax({
4693                 url: NETDATA.google_js,
4694                 cache: true,
4695                 dataType: "script",
4696                 xhrFields: { withCredentials: true } // required for the cookie
4697             })
4698             .done(function() {
4699                 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4700                 google.load('visualization', '1.1', {
4701                     'packages': ['corechart', 'controls'],
4702                     'callback': callback
4703                 });
4704             })
4705             .fail(function() {
4706                 NETDATA.chartLibraries.google.enabled = false;
4707                 NETDATA.error(100, NETDATA.google_js);
4708                 if(typeof callback === "function")
4709                     callback();
4710             });
4711         }
4712         else {
4713             NETDATA.chartLibraries.google.enabled = false;
4714             if(typeof callback === "function")
4715                 callback();
4716         }
4717     };
4718
4719     NETDATA.googleChartUpdate = function(state, data) {
4720         var datatable = new google.visualization.DataTable(data.result);
4721         state.google_instance.draw(datatable, state.google_options);
4722         return true;
4723     };
4724
4725     NETDATA.googleChartCreate = function(state, data) {
4726         var datatable = new google.visualization.DataTable(data.result);
4727
4728         state.google_options = {
4729             colors: state.chartColors(),
4730
4731             // do not set width, height - the chart resizes itself
4732             //width: state.chartWidth(),
4733             //height: state.chartHeight(),
4734             lineWidth: 1,
4735             title: state.title,
4736             fontSize: 11,
4737             hAxis: {
4738             //  title: "Time of Day",
4739             //  format:'HH:mm:ss',
4740                 viewWindowMode: 'maximized',
4741                 slantedText: false,
4742                 format:'HH:mm:ss',
4743                 textStyle: {
4744                     fontSize: 9
4745                 },
4746                 gridlines: {
4747                     color: '#EEE'
4748                 }
4749             },
4750             vAxis: {
4751                 title: state.units,
4752                 viewWindowMode: 'pretty',
4753                 minValue: -0.1,
4754                 maxValue: 0.1,
4755                 direction: 1,
4756                 textStyle: {
4757                     fontSize: 9
4758                 },
4759                 gridlines: {
4760                     color: '#EEE'
4761                 }
4762             },
4763             chartArea: {
4764                 width: '65%',
4765                 height: '80%'
4766             },
4767             focusTarget: 'category',
4768             annotation: {
4769                 '1': {
4770                     style: 'line'
4771                 }
4772             },
4773             pointsVisible: 0,
4774             titlePosition: 'out',
4775             titleTextStyle: {
4776                 fontSize: 11
4777             },
4778             tooltip: {
4779                 isHtml: false,
4780                 ignoreBounds: true,
4781                 textStyle: {
4782                     fontSize: 9
4783                 }
4784             },
4785             curveType: 'function',
4786             areaOpacity: 0.3,
4787             isStacked: false
4788         };
4789
4790         switch(state.chart.chart_type) {
4791             case "area":
4792                 state.google_options.vAxis.viewWindowMode = 'maximized';
4793                 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4794                 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4795                 break;
4796
4797             case "stacked":
4798                 state.google_options.isStacked = true;
4799                 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4800                 state.google_options.vAxis.viewWindowMode = 'maximized';
4801                 state.google_options.vAxis.minValue = null;
4802                 state.google_options.vAxis.maxValue = null;
4803                 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4804                 break;
4805
4806             default:
4807             case "line":
4808                 state.google_options.lineWidth = 2;
4809                 state.google_instance = new google.visualization.LineChart(state.element_chart);
4810                 break;
4811         }
4812
4813         state.google_instance.draw(datatable, state.google_options);
4814         return true;
4815     };
4816
4817     // ----------------------------------------------------------------------------------------------------------------
4818
4819     NETDATA.percentFromValueMax = function(value, max) {
4820         if(value === null) value = 0;
4821         if(max < value) max = value;
4822
4823         var pcent = 0;
4824         if(max !== 0) {
4825             pcent = Math.round(value * 100 / max);
4826             if(pcent === 0 && value > 0) pcent = 1;
4827         }
4828
4829         return pcent;
4830     };
4831
4832     // ----------------------------------------------------------------------------------------------------------------
4833     // easy-pie-chart
4834
4835     NETDATA.easypiechartInitialize = function(callback) {
4836         if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4837             $.ajax({
4838                 url: NETDATA.easypiechart_js,
4839                 cache: true,
4840                 dataType: "script",
4841                 xhrFields: { withCredentials: true } // required for the cookie
4842             })
4843                 .done(function() {
4844                     NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4845                 })
4846                 .fail(function() {
4847                     NETDATA.chartLibraries.easypiechart.enabled = false;
4848                     NETDATA.error(100, NETDATA.easypiechart_js);
4849                 })
4850                 .always(function() {
4851                     if(typeof callback === "function")
4852                         callback();
4853                 })
4854         }
4855         else {
4856             NETDATA.chartLibraries.easypiechart.enabled = false;
4857             if(typeof callback === "function")
4858                 callback();
4859         }
4860     };
4861
4862     NETDATA.easypiechartClearSelection = function(state) {
4863         if(typeof state.easyPieChartEvent !== 'undefined') {
4864             if(state.easyPieChartEvent.timer !== null)
4865                 clearTimeout(state.easyPieChartEvent.timer);
4866
4867             state.easyPieChartEvent.timer = null;
4868         }
4869
4870         if(state.isAutoRefreshable() === true && state.data !== null) {
4871             NETDATA.easypiechartChartUpdate(state, state.data);
4872         }
4873         else {
4874             state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4875             state.easyPieChart_instance.update(0);
4876         }
4877         state.easyPieChart_instance.enableAnimation();
4878
4879         return true;
4880     };
4881
4882     NETDATA.easypiechartSetSelection = function(state, t) {
4883         if(state.timeIsVisible(t) !== true)
4884             return NETDATA.easypiechartClearSelection(state);
4885
4886         var slot = state.calculateRowForTime(t);
4887         if(slot < 0 || slot >= state.data.result.length)
4888             return NETDATA.easypiechartClearSelection(state);
4889
4890         if(typeof state.easyPieChartEvent === 'undefined') {
4891             state.easyPieChartEvent = {
4892                 timer: null,
4893                 value: 0,
4894                 pcent: 0
4895             };
4896         }
4897
4898         var value = state.data.result[state.data.result.length - 1 - slot];
4899         var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4900         var pcent = NETDATA.percentFromValueMax(value, max);
4901
4902         state.easyPieChartEvent.value = value;
4903         state.easyPieChartEvent.pcent = pcent;
4904         state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4905
4906         if(state.easyPieChartEvent.timer === null) {
4907             state.easyPieChart_instance.disableAnimation();
4908
4909             state.easyPieChartEvent.timer = setTimeout(function() {
4910                 state.easyPieChartEvent.timer = null;
4911                 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4912             }, NETDATA.options.current.charts_selection_animation_delay);
4913         }
4914
4915         return true;
4916     };
4917
4918     NETDATA.easypiechartChartUpdate = function(state, data) {
4919         var value, max, pcent;
4920
4921         if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
4922             value = null;
4923             max = 0;
4924             pcent = 0;
4925         }
4926         else {
4927             value = data.result[0];
4928             max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4929             pcent = NETDATA.percentFromValueMax(value, max);
4930         }
4931
4932         state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4933         state.easyPieChart_instance.update(pcent);
4934         return true;
4935     };
4936
4937     NETDATA.easypiechartChartCreate = function(state, data) {
4938         var self = $(state.element);
4939         var chart = $(state.element_chart);
4940
4941         var value = data.result[0];
4942         var max = self.data('easypiechart-max-value') || null;
4943         var adjust = self.data('easypiechart-adjust') || null;
4944
4945         if(max === null) {
4946             max = data.max;
4947             state.easyPieChartMax = null;
4948         }
4949         else
4950             state.easyPieChartMax = max;
4951
4952         var pcent = NETDATA.percentFromValueMax(value, max);
4953
4954         chart.data('data-percent', pcent);
4955
4956         var size;
4957         switch(adjust) {
4958             case 'width': size = state.chartHeight(); break;
4959             case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4960             case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4961             case 'height':
4962             default: size = state.chartWidth(); break;
4963         }
4964         state.element.style.width = size + 'px';
4965         state.element.style.height = size + 'px';
4966
4967         var stroke = Math.floor(size / 22);
4968         if(stroke < 3) stroke = 2;
4969
4970         var valuefontsize = Math.floor((size * 2 / 3) / 5);
4971         var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4972         state.easyPieChartLabel = document.createElement('span');
4973         state.easyPieChartLabel.className = 'easyPieChartLabel';
4974         state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4975         state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4976         state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4977         state.element_chart.appendChild(state.easyPieChartLabel);
4978
4979         var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4980         var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4981         state.easyPieChartTitle = document.createElement('span');
4982         state.easyPieChartTitle.className = 'easyPieChartTitle';
4983         state.easyPieChartTitle.innerHTML = state.title;
4984         state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4985         state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4986         state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4987         state.element_chart.appendChild(state.easyPieChartTitle);
4988
4989         var unitfontsize = Math.round(titlefontsize * 0.9);
4990         var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4991         state.easyPieChartUnits = document.createElement('span');
4992         state.easyPieChartUnits.className = 'easyPieChartUnits';
4993         state.easyPieChartUnits.innerHTML = state.units;
4994         state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4995         state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4996         state.element_chart.appendChild(state.easyPieChartUnits);
4997
4998         chart.easyPieChart({
4999             barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
5000             trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5001             scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5002             scaleLength: self.data('easypiechart-scalelength') || 5,
5003             lineCap: self.data('easypiechart-linecap') || 'round',
5004             lineWidth: self.data('easypiechart-linewidth') || stroke,
5005             trackWidth: self.data('easypiechart-trackwidth') || undefined,
5006             size: self.data('easypiechart-size') || size,
5007             rotate: self.data('easypiechart-rotate') || 0,
5008             animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
5009             easing: self.data('easypiechart-easing') || undefined
5010         });
5011
5012         // when we just re-create the chart
5013         // do not animate the first update
5014         var animate = true;
5015         if(typeof state.easyPieChart_instance !== 'undefined')
5016             animate = false;
5017
5018         state.easyPieChart_instance = chart.data('easyPieChart');
5019         if(animate === false) state.easyPieChart_instance.disableAnimation();
5020         state.easyPieChart_instance.update(pcent);
5021         if(animate === false) state.easyPieChart_instance.enableAnimation();
5022         return true;
5023     };
5024
5025     // ----------------------------------------------------------------------------------------------------------------
5026     // gauge.js
5027
5028     NETDATA.gaugeInitialize = function(callback) {
5029         if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5030             $.ajax({
5031                 url: NETDATA.gauge_js,
5032                 cache: true,
5033                 dataType: "script",
5034                 xhrFields: { withCredentials: true } // required for the cookie
5035             })
5036                 .done(function() {
5037                     NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5038                 })
5039                 .fail(function() {
5040                     NETDATA.chartLibraries.gauge.enabled = false;
5041                     NETDATA.error(100, NETDATA.gauge_js);
5042                 })
5043                 .always(function() {
5044                     if(typeof callback === "function")
5045                         callback();
5046                 })
5047         }
5048         else {
5049             NETDATA.chartLibraries.gauge.enabled = false;
5050             if(typeof callback === "function")
5051                 callback();
5052         }
5053     };
5054
5055     NETDATA.gaugeAnimation = function(state, status) {
5056         var speed = 32;
5057
5058         if(typeof status === 'boolean' && status === false)
5059             speed = 1000000000;
5060         else if(typeof status === 'number')
5061             speed = status;
5062
5063         // console.log('gauge speed ' + speed);
5064         state.gauge_instance.animationSpeed = speed;
5065         state.___gaugeOld__.speed = speed;
5066     };
5067
5068     NETDATA.gaugeSet = function(state, value, min, max) {
5069         if(typeof value !== 'number') value = 0;
5070         if(typeof min !== 'number') min = 0;
5071         if(typeof max !== 'number') max = 0;
5072         if(value > max) max = value;
5073         if(value < min) min = value;
5074         if(min > max) {
5075             var t = min;
5076             min = max;
5077             max = t;
5078         }
5079         else if(min == max)
5080             max = min + 1;
5081
5082         // gauge.js has an issue if the needle
5083         // is smaller than min or larger than max
5084         // when we set the new values
5085         // the needle will go crazy
5086
5087         // to prevent it, we always feed it
5088         // with a percentage, so that the needle
5089         // is always between min and max
5090         var pcent = (value - min) * 100 / (max - min);
5091
5092         // these should never happen
5093         if(pcent < 0) pcent = 0;
5094         if(pcent > 100) pcent = 100;
5095
5096         state.gauge_instance.set(pcent);
5097         // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5098
5099         state.___gaugeOld__.value = value;
5100         state.___gaugeOld__.min = min;
5101         state.___gaugeOld__.max = max;
5102     };
5103
5104     NETDATA.gaugeSetLabels = function(state, value, min, max) {
5105         if(state.___gaugeOld__.valueLabel !== value) {
5106             state.___gaugeOld__.valueLabel = value;
5107             state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
5108         }
5109         if(state.___gaugeOld__.minLabel !== min) {
5110             state.___gaugeOld__.minLabel = min;
5111             state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
5112         }
5113         if(state.___gaugeOld__.maxLabel !== max) {
5114             state.___gaugeOld__.maxLabel = max;
5115             state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
5116         }
5117     };
5118
5119     NETDATA.gaugeClearSelection = function(state) {
5120         if(typeof state.gaugeEvent !== 'undefined') {
5121             if(state.gaugeEvent.timer !== null)
5122                 clearTimeout(state.gaugeEvent.timer);
5123
5124             state.gaugeEvent.timer = null;
5125         }
5126
5127         if(state.isAutoRefreshable() === true && state.data !== null) {
5128             NETDATA.gaugeChartUpdate(state, state.data);
5129         }
5130         else {
5131             NETDATA.gaugeAnimation(state, false);
5132             NETDATA.gaugeSet(state, null, null, null);
5133             NETDATA.gaugeSetLabels(state, null, null, null);
5134         }
5135
5136         NETDATA.gaugeAnimation(state, true);
5137         return true;
5138     };
5139
5140     NETDATA.gaugeSetSelection = function(state, t) {
5141         if(state.timeIsVisible(t) !== true)
5142             return NETDATA.gaugeClearSelection(state);
5143
5144         var slot = state.calculateRowForTime(t);
5145         if(slot < 0 || slot >= state.data.result.length)
5146             return NETDATA.gaugeClearSelection(state);
5147
5148         if(typeof state.gaugeEvent === 'undefined') {
5149             state.gaugeEvent = {
5150                 timer: null,
5151                 value: 0,
5152                 min: 0,
5153                 max: 0
5154             };
5155         }
5156
5157         var value = state.data.result[state.data.result.length - 1 - slot];
5158         var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
5159         var min = 0;
5160
5161         state.gaugeEvent.value = value;
5162         state.gaugeEvent.max = max;
5163         state.gaugeEvent.min = min;
5164         NETDATA.gaugeSetLabels(state, value, min, max);
5165
5166         if(state.gaugeEvent.timer === null) {
5167             NETDATA.gaugeAnimation(state, false);
5168
5169             state.gaugeEvent.timer = setTimeout(function() {
5170                 state.gaugeEvent.timer = null;
5171                 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5172             }, NETDATA.options.current.charts_selection_animation_delay);
5173         }
5174
5175         return true;
5176     };
5177
5178     NETDATA.gaugeChartUpdate = function(state, data) {
5179         var value, min, max;
5180
5181         if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5182             value = 0;
5183             min = 0;
5184             max = 1;
5185             NETDATA.gaugeSetLabels(state, null, null, null);
5186         }
5187         else {
5188             value = data.result[0];
5189             min = 0;
5190             max = (state.gaugeMax === null)?data.max:state.gaugeMax;
5191             if(value > max) max = value;
5192             NETDATA.gaugeSetLabels(state, value, min, max);
5193         }
5194
5195         NETDATA.gaugeSet(state, value, min, max);
5196         return true;
5197     };
5198
5199     NETDATA.gaugeChartCreate = function(state, data) {
5200         var self = $(state.element);
5201         // var chart = $(state.element_chart);
5202
5203         var value = data.result[0];
5204         var max = self.data('gauge-max-value') || null;
5205         var adjust = self.data('gauge-adjust') || null;
5206         var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5207         var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5208         var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5209         var stopColor = self.data('gauge-stop-color') || void 0;
5210         var generateGradient = self.data('gauge-generate-gradient') || false;
5211
5212         if(max === null) {
5213             max = data.max;
5214             state.gaugeMax = null;
5215         }
5216         else
5217             state.gaugeMax = max;
5218
5219         var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5220         //switch(adjust) {
5221         //  case 'width': width = height * ratio; break;
5222         //  case 'height':
5223         //  default: height = width / ratio; break;
5224         //}
5225         //state.element.style.width = width.toString() + 'px';
5226         //state.element.style.height = height.toString() + 'px';
5227
5228         var lum_d = 0.05;
5229
5230         var options = {
5231             lines: 12,                  // The number of lines to draw
5232             angle: 0.15,                // The length of each line
5233             lineWidth: 0.44,            // 0.44 The line thickness
5234             pointer: {
5235                 length: 0.8,            // 0.9 The radius of the inner circle
5236                 strokeWidth: 0.035,     // The rotation offset
5237                 color: pointerColor     // Fill color
5238             },
5239             colorStart: startColor,     // Colors
5240             colorStop: stopColor,       // just experiment with them
5241             strokeColor: strokeColor,   // to see which ones work best for you
5242             limitMax: true,
5243             generateGradient: (generateGradient === true)?true:false,
5244             gradientType: 0
5245         };
5246
5247         if (generateGradient.constructor === Array) {
5248             // example options:
5249             // data-gauge-generate-gradient="[0, 50, 100]"
5250             // data-gauge-gradient-percent-color-0="#FFFFFF"
5251             // data-gauge-gradient-percent-color-50="#999900"
5252             // data-gauge-gradient-percent-color-100="#000000"
5253
5254             options.percentColors = new Array();
5255             var len = generateGradient.length;
5256             while(len--) {
5257                 var pcent = generateGradient[len];
5258                 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5259                 if(color !== false) {
5260                     var a = new Array();
5261                     a[0] = pcent / 100;
5262                     a[1] = color;
5263                     options.percentColors.unshift(a);
5264                 }
5265             }
5266             if(options.percentColors.length === 0)
5267                 delete options.percentColors;
5268         }
5269         else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5270             options.percentColors = [
5271                 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5272                 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5273                 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5274                 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5275                 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5276                 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5277                 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5278                 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5279                 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5280                 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5281                 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5282         }
5283
5284         state.gauge_canvas = document.createElement('canvas');
5285         state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5286         state.gauge_canvas.className = 'gaugeChart';
5287         state.gauge_canvas.width  = width;
5288         state.gauge_canvas.height = height;
5289         state.element_chart.appendChild(state.gauge_canvas);
5290
5291         var valuefontsize = Math.floor(height / 6);
5292         var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5293         state.gaugeChartLabel = document.createElement('span');
5294         state.gaugeChartLabel.className = 'gaugeChartLabel';
5295         state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5296         state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5297         state.element_chart.appendChild(state.gaugeChartLabel);
5298
5299         var titlefontsize = Math.round(valuefontsize / 2);
5300         var titletop = 0;
5301         state.gaugeChartTitle = document.createElement('span');
5302         state.gaugeChartTitle.className = 'gaugeChartTitle';
5303         state.gaugeChartTitle.innerHTML = state.title;
5304         state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5305         state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5306         state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5307         state.element_chart.appendChild(state.gaugeChartTitle);
5308
5309         var unitfontsize = Math.round(titlefontsize * 0.9);
5310         state.gaugeChartUnits = document.createElement('span');
5311         state.gaugeChartUnits.className = 'gaugeChartUnits';
5312         state.gaugeChartUnits.innerHTML = state.units;
5313         state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5314         state.element_chart.appendChild(state.gaugeChartUnits);
5315
5316         state.gaugeChartMin = document.createElement('span');
5317         state.gaugeChartMin.className = 'gaugeChartMin';
5318         state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5319         state.element_chart.appendChild(state.gaugeChartMin);
5320
5321         state.gaugeChartMax = document.createElement('span');
5322         state.gaugeChartMax.className = 'gaugeChartMax';
5323         state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5324         state.element_chart.appendChild(state.gaugeChartMax);
5325
5326         // when we just re-create the chart
5327         // do not animate the first update
5328         var animate = true;
5329         if(typeof state.gauge_instance !== 'undefined')
5330             animate = false;
5331
5332         state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5333
5334         state.___gaugeOld__ = {
5335             value: value,
5336             min: 0,
5337             max: max,
5338             valueLabel: null,
5339             minLabel: null,
5340             maxLabel: null
5341         };
5342
5343         // we will always feed a percentage
5344         state.gauge_instance.minValue = 0;
5345         state.gauge_instance.maxValue = 100;
5346
5347         NETDATA.gaugeAnimation(state, animate);
5348         NETDATA.gaugeSet(state, value, 0, max);
5349         NETDATA.gaugeSetLabels(state, value, 0, max);
5350         NETDATA.gaugeAnimation(state, true);
5351         return true;
5352     };
5353
5354     // ----------------------------------------------------------------------------------------------------------------
5355     // Charts Libraries Registration
5356
5357     NETDATA.chartLibraries = {
5358         "dygraph": {
5359             initialize: NETDATA.dygraphInitialize,
5360             create: NETDATA.dygraphChartCreate,
5361             update: NETDATA.dygraphChartUpdate,
5362             resize: function(state) {
5363                 if(typeof state.dygraph_instance.resize === 'function')
5364                     state.dygraph_instance.resize();
5365             },
5366             setSelection: NETDATA.dygraphSetSelection,
5367             clearSelection:  NETDATA.dygraphClearSelection,
5368             toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5369             initialized: false,
5370             enabled: true,
5371             format: function(state) { return 'json'; },
5372             options: function(state) { return 'ms|flip'; },
5373             legend: function(state) {
5374                 if(this.isSparkline(state) === false)
5375                     return 'right-side';
5376                 else
5377                     return null;
5378             },
5379             autoresize: function(state) { return true; },
5380             max_updates_to_recreate: function(state) { return 5000; },
5381             track_colors: function(state) { return true; },
5382             pixels_per_point: function(state) {
5383                 if(this.isSparkline(state) === false)
5384                     return 3;
5385                 else
5386                     return 2;
5387             },
5388
5389             isSparkline: function(state) {
5390                 if(typeof state.dygraph_sparkline === 'undefined') {
5391                     var t = $(state.element).data('dygraph-theme');
5392                     if(t === 'sparkline')
5393                         state.dygraph_sparkline = true;
5394                     else
5395                         state.dygraph_sparkline = false;
5396                 }
5397                 return state.dygraph_sparkline;
5398             }
5399         },
5400         "sparkline": {
5401             initialize: NETDATA.sparklineInitialize,
5402             create: NETDATA.sparklineChartCreate,
5403             update: NETDATA.sparklineChartUpdate,
5404             resize: null,
5405             setSelection: undefined, // function(state, t) { return true; },
5406             clearSelection: undefined, // function(state) { return true; },
5407             toolboxPanAndZoom: null,
5408             initialized: false,
5409             enabled: true,
5410             format: function(state) { return 'array'; },
5411             options: function(state) { return 'flip|abs'; },
5412             legend: function(state) { return null; },
5413             autoresize: function(state) { return false; },
5414             max_updates_to_recreate: function(state) { return 5000; },
5415             track_colors: function(state) { return false; },
5416             pixels_per_point: function(state) { return 3; }
5417         },
5418         "peity": {
5419             initialize: NETDATA.peityInitialize,
5420             create: NETDATA.peityChartCreate,
5421             update: NETDATA.peityChartUpdate,
5422             resize: null,
5423             setSelection: undefined, // function(state, t) { return true; },
5424             clearSelection: undefined, // function(state) { return true; },
5425             toolboxPanAndZoom: null,
5426             initialized: false,
5427             enabled: true,
5428             format: function(state) { return 'ssvcomma'; },
5429             options: function(state) { return 'null2zero|flip|abs'; },
5430             legend: function(state) { return null; },
5431             autoresize: function(state) { return false; },
5432             max_updates_to_recreate: function(state) { return 5000; },
5433             track_colors: function(state) { return false; },
5434             pixels_per_point: function(state) { return 3; }
5435         },
5436         "morris": {
5437             initialize: NETDATA.morrisInitialize,
5438             create: NETDATA.morrisChartCreate,
5439             update: NETDATA.morrisChartUpdate,
5440             resize: null,
5441             setSelection: undefined, // function(state, t) { return true; },
5442             clearSelection: undefined, // function(state) { return true; },
5443             toolboxPanAndZoom: null,
5444             initialized: false,
5445             enabled: true,
5446             format: function(state) { return 'json'; },
5447             options: function(state) { return 'objectrows|ms'; },
5448             legend: function(state) { return null; },
5449             autoresize: function(state) { return false; },
5450             max_updates_to_recreate: function(state) { return 50; },
5451             track_colors: function(state) { return false; },
5452             pixels_per_point: function(state) { return 15; }
5453         },
5454         "google": {
5455             initialize: NETDATA.googleInitialize,
5456             create: NETDATA.googleChartCreate,
5457             update: NETDATA.googleChartUpdate,
5458             resize: null,
5459             setSelection: undefined, //function(state, t) { return true; },
5460             clearSelection: undefined, //function(state) { return true; },
5461             toolboxPanAndZoom: null,
5462             initialized: false,
5463             enabled: true,
5464             format: function(state) { return 'datatable'; },
5465             options: function(state) { return ''; },
5466             legend: function(state) { return null; },
5467             autoresize: function(state) { return false; },
5468             max_updates_to_recreate: function(state) { return 300; },
5469             track_colors: function(state) { return false; },
5470             pixels_per_point: function(state) { return 4; }
5471         },
5472         "raphael": {
5473             initialize: NETDATA.raphaelInitialize,
5474             create: NETDATA.raphaelChartCreate,
5475             update: NETDATA.raphaelChartUpdate,
5476             resize: null,
5477             setSelection: undefined, // function(state, t) { return true; },
5478             clearSelection: undefined, // function(state) { return true; },
5479             toolboxPanAndZoom: null,
5480             initialized: false,
5481             enabled: true,
5482             format: function(state) { return 'json'; },
5483             options: function(state) { return ''; },
5484             legend: function(state) { return null; },
5485             autoresize: function(state) { return false; },
5486             max_updates_to_recreate: function(state) { return 5000; },
5487             track_colors: function(state) { return false; },
5488             pixels_per_point: function(state) { return 3; }
5489         },
5490         "c3": {
5491             initialize: NETDATA.c3Initialize,
5492             create: NETDATA.c3ChartCreate,
5493             update: NETDATA.c3ChartUpdate,
5494             resize: null,
5495             setSelection: undefined, // function(state, t) { return true; },
5496             clearSelection: undefined, // function(state) { return true; },
5497             toolboxPanAndZoom: null,
5498             initialized: false,
5499             enabled: true,
5500             format: function(state) { return 'csvjsonarray'; },
5501             options: function(state) { return 'milliseconds'; },
5502             legend: function(state) { return null; },
5503             autoresize: function(state) { return false; },
5504             max_updates_to_recreate: function(state) { return 5000; },
5505             track_colors: function(state) { return false; },
5506             pixels_per_point: function(state) { return 15; }
5507         },
5508         "d3": {
5509             initialize: NETDATA.d3Initialize,
5510             create: NETDATA.d3ChartCreate,
5511             update: NETDATA.d3ChartUpdate,
5512             resize: null,
5513             setSelection: undefined, // function(state, t) { return true; },
5514             clearSelection: undefined, // function(state) { return true; },
5515             toolboxPanAndZoom: null,
5516             initialized: false,
5517             enabled: true,
5518             format: function(state) { return 'json'; },
5519             options: function(state) { return ''; },
5520             legend: function(state) { return null; },
5521             autoresize: function(state) { return false; },
5522             max_updates_to_recreate: function(state) { return 5000; },
5523             track_colors: function(state) { return false; },
5524             pixels_per_point: function(state) { return 3; }
5525         },
5526         "easypiechart": {
5527             initialize: NETDATA.easypiechartInitialize,
5528             create: NETDATA.easypiechartChartCreate,
5529             update: NETDATA.easypiechartChartUpdate,
5530             resize: null,
5531             setSelection: NETDATA.easypiechartSetSelection,
5532             clearSelection: NETDATA.easypiechartClearSelection,
5533             toolboxPanAndZoom: null,
5534             initialized: false,
5535             enabled: true,
5536             format: function(state) { return 'array'; },
5537             options: function(state) { return 'absolute'; },
5538             legend: function(state) { return null; },
5539             autoresize: function(state) { return false; },
5540             max_updates_to_recreate: function(state) { return 5000; },
5541             track_colors: function(state) { return true; },
5542             pixels_per_point: function(state) { return 3; },
5543             aspect_ratio: 100
5544         },
5545         "gauge": {
5546             initialize: NETDATA.gaugeInitialize,
5547             create: NETDATA.gaugeChartCreate,
5548             update: NETDATA.gaugeChartUpdate,
5549             resize: null,
5550             setSelection: NETDATA.gaugeSetSelection,
5551             clearSelection: NETDATA.gaugeClearSelection,
5552             toolboxPanAndZoom: null,
5553             initialized: false,
5554             enabled: true,
5555             format: function(state) { return 'array'; },
5556             options: function(state) { return 'absolute'; },
5557             legend: function(state) { return null; },
5558             autoresize: function(state) { return false; },
5559             max_updates_to_recreate: function(state) { return 5000; },
5560             track_colors: function(state) { return true; },
5561             pixels_per_point: function(state) { return 3; },
5562             aspect_ratio: 70
5563         }
5564     };
5565
5566     NETDATA.registerChartLibrary = function(library, url) {
5567         if(NETDATA.options.debug.libraries === true)
5568             console.log("registering chart library: " + library);
5569
5570         NETDATA.chartLibraries[library].url = url;
5571         NETDATA.chartLibraries[library].initialized = true;
5572         NETDATA.chartLibraries[library].enabled = true;
5573     };
5574
5575     // ----------------------------------------------------------------------------------------------------------------
5576     // Load required JS libraries and CSS
5577
5578     NETDATA.requiredJs = [
5579         {
5580             url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5581             async: false,
5582             isAlreadyLoaded: function() {
5583                 // check if bootstrap is loaded
5584                 if(typeof $().emulateTransitionEnd == 'function')
5585                     return true;
5586                 else {
5587                     if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5588                         return true;
5589                     else
5590                         return false;
5591                 }
5592             }
5593         },
5594         {
5595             url: NETDATA.serverDefault + 'lib/jquery.nanoscroller-0.8.7.min.js',
5596             isAlreadyLoaded: function() { return false; }
5597         }
5598     ];
5599
5600     NETDATA.requiredCSS = [
5601         {
5602             url: NETDATA.themes.current.bootstrap_css,
5603             isAlreadyLoaded: function() {
5604                 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5605                     return true;
5606                 else
5607                     return false;
5608             }
5609         },
5610         {
5611             url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.6.3',
5612             isAlreadyLoaded: function() { return false; }
5613         },
5614         {
5615             url: NETDATA.themes.current.dashboard_css,
5616             isAlreadyLoaded: function() { return false; }
5617         }
5618     ];
5619
5620     NETDATA.loadedRequiredJs = 0;
5621     NETDATA.loadRequiredJs = function(index, callback) {
5622         if(index >= NETDATA.requiredJs.length) {
5623             if(typeof callback === 'function')
5624                 callback();
5625             return;
5626         }
5627
5628         if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5629             NETDATA.loadedRequiredJs++;
5630             NETDATA.loadRequiredJs(++index, callback);
5631             return;
5632         }
5633
5634         if(NETDATA.options.debug.main_loop === true)
5635             console.log('loading ' + NETDATA.requiredJs[index].url);
5636
5637         var async = true;
5638         if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5639             async = false;
5640
5641         $.ajax({
5642             url: NETDATA.requiredJs[index].url,
5643             cache: true,
5644             dataType: "script",
5645             xhrFields: { withCredentials: true } // required for the cookie
5646         })
5647         .done(function() {
5648             if(NETDATA.options.debug.main_loop === true)
5649                 console.log('loaded ' + NETDATA.requiredJs[index].url);
5650         })
5651         .fail(function() {
5652             alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5653         })
5654         .always(function() {
5655             NETDATA.loadedRequiredJs++;
5656
5657             if(async === false)
5658                 NETDATA.loadRequiredJs(++index, callback);
5659         })
5660
5661         if(async === true)
5662             NETDATA.loadRequiredJs(++index, callback);
5663     };
5664
5665     NETDATA.loadRequiredCSS = function(index) {
5666         if(index >= NETDATA.requiredCSS.length)
5667             return;
5668
5669         if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5670             NETDATA.loadRequiredCSS(++index);
5671             return;
5672         }
5673
5674         if(NETDATA.options.debug.main_loop === true)
5675             console.log('loading ' + NETDATA.requiredCSS[index].url);
5676
5677         NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5678         NETDATA.loadRequiredCSS(++index);
5679     };
5680
5681
5682     // ----------------------------------------------------------------------------------------------------------------
5683     // Registry of netdata hosts
5684
5685     NETDATA.alarms = {
5686         onclick: null,                  // the callback to handle the click - it will be called with the alarm log entry
5687         chart_div_offset: 100,          // give that space above the chart when scrolling to it
5688         chart_div_id_prefix: 'chart_',  // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
5689         chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
5690
5691         ms_penalty: 0,                  // the time penalty of the next alarm
5692         ms_between_notifications: 500,  // firefox moves the alarms off-screen (above, outside the top of the screen)
5693                                         // if alarms are shown faster than: one per 500ms
5694
5695         notifications: false,           // when true, the browser supports notifications (may not be granted though)
5696         last_notification_id: 0,        // the id of the last alarm_log we have raised an alarm for
5697         first_notification_id: 0,       // the id of the first alarm_log entry for this session
5698                                         // this is used to prevent CLEAR notifications for past events
5699         // notifications_shown: new Array(),
5700
5701         server: null,                   // the server to connect to for fetching alarms
5702         current: null,                  // the list of raised alarms - updated in the background
5703         callback: null,                 // a callback function to call every time the list of raised alarms is refreshed
5704
5705         notify: function(entry) {
5706             // console.log('alarm ' + entry.unique_id);
5707
5708             if(entry.updated === true) {
5709                 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
5710                 return;
5711             }
5712
5713             var value = entry.value;
5714             if(NETDATA.alarms.current !== null) {
5715                 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
5716                 if(typeof t !== 'undefined' && entry.status == t.status)
5717                     value = t.value;
5718             }
5719
5720             var name = entry.name.replace(/_/g, ' ');
5721             var status = entry.status.toLowerCase();
5722             var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
5723             var tag = entry.alarm_id;
5724             var icon = 'images/seo-performance-128.png';
5725             var interaction = false;
5726             var data = entry;
5727             var show = true;
5728
5729             // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' +  entry.status);
5730
5731             switch(entry.status) {
5732                 case 'REMOVED':
5733                     show = false;
5734                     break;
5735
5736                 case 'UNDEFINED':
5737                     return;
5738
5739                 case 'UNINITIALIZED':
5740                     return;
5741
5742                 case 'CLEAR':
5743                     if(entry.unique_id < NETDATA.alarms.first_notification_id) {
5744                         // console.log('alarm ' + entry.unique_id + ' is not current');
5745                         return;
5746                     }
5747                     if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
5748                         // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
5749                         return;
5750                     }
5751                     title = name + ' back to normal';
5752                     icon = 'images/check-mark-2-128-green.png'
5753                     interaction = false;
5754                     break;
5755
5756                 case 'WARNING':
5757                     if(entry.old_status === 'CRITICAL')
5758                         status = 'demoted to ' + entry.status.toLowerCase();
5759
5760                     icon = 'images/alert-128-orange.png';
5761                     interaction = false;
5762                     break;
5763
5764                 case 'CRITICAL':
5765                     if(entry.old_status === 'WARNING')
5766                         status = 'escalated to ' + entry.status.toLowerCase();
5767                     
5768                     icon = 'images/alert-128-red.png'
5769                     interaction = true;
5770                     break;
5771
5772                 default:
5773                     console.log('invalid alarm status ' + entry.status);
5774                     return;
5775             }
5776
5777             /*
5778             // cleanup old notifications with the same alarm_id as this one
5779             // FIXME: it does not seem to work on any web browser!
5780             var len = NETDATA.alarms.notifications_shown.length;
5781             while(len--) {
5782                 var n = NETDATA.alarms.notifications_shown[len];
5783                 if(n.data.alarm_id === entry.alarm_id) {
5784                     console.log('removing old alarm ' + n.data.unique_id);
5785
5786                     // close the notification
5787                     n.close.bind(n);
5788
5789                     // remove it from the array
5790                     NETDATA.alarms.notifications_shown.splice(len, 1);
5791                     len = NETDATA.alarms.notifications_shown.length;
5792                 }
5793             }
5794             */
5795
5796             if(show === true) {
5797
5798                 setTimeout(function() {
5799                     // show this notification
5800                     // console.log('new notification: ' + title);
5801                     var n = new Notification(title, {
5802                         body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
5803                         tag: tag,
5804                         requireInteraction: interaction,
5805                         icon: NETDATA.serverDefault + icon,
5806                         data: data
5807                     });
5808
5809                     n.onclick = function(event) {
5810                         event.preventDefault();
5811                         NETDATA.alarms.onclick(event.target.data);
5812                     };
5813
5814                     // console.log(n);
5815                     // NETDATA.alarms.notifications_shown.push(n);
5816                     // console.log(entry);
5817                 }, NETDATA.alarms.ms_penalty);
5818
5819                 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
5820             }
5821         },
5822
5823         scrollToChart: function(chart_id) {
5824             if(typeof chart_id === 'string') {
5825                 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
5826                 if(typeof offset !== 'undefined') {
5827                     $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
5828                     return true;
5829                 }
5830             }
5831             return false;
5832         },
5833
5834         scrollToAlarm: function(alarm) {
5835             if(typeof alarm === 'object') {
5836                 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
5837
5838                 if(ret === true && NETDATA.options.page_is_visible === false)
5839                     window.focus();
5840                 //    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.');
5841             }
5842
5843         },
5844
5845         notifyAll: function() {
5846             // console.log('FETCHING ALARM LOG');
5847             NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
5848                 // console.log('ALARM LOG FETCHED');
5849
5850                 if(data === null || typeof data !== 'object') {
5851                     console.log('invalid alarms log response');
5852                     return;
5853                 }
5854
5855                 if(data.length === 0) {
5856                     console.log('received empty alarm log');
5857                     return;
5858                 }
5859
5860                 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
5861
5862                 data.sort(function(a, b) {
5863                     if(a.unique_id > b.unique_id) return -1;
5864                     if(a.unique_id < b.unique_id) return 1;
5865                     return 0;
5866                 });
5867
5868                 NETDATA.alarms.ms_penalty = 0;
5869
5870                 var len = data.length;
5871                 while(len--) {
5872                     if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
5873                         NETDATA.alarms.notify(data[len]);
5874                     }
5875                     //else
5876                     //    console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
5877                 }
5878
5879                 NETDATA.alarms.last_notification_id = data[0].unique_id;
5880                 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
5881                 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
5882             })
5883         },
5884
5885         check_notifications: function() {
5886             // returns true if we should fire 1+ notifications
5887
5888             if(NETDATA.alarms.notifications !== true) {
5889                 // console.log('notifications not available');
5890                 return false;
5891             }
5892
5893             if(Notification.permission !== 'granted') {
5894                 // console.log('notifications not granted');
5895                 return false;
5896             }
5897
5898             if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
5899                 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
5900
5901                 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
5902                     // console.log('new alarms detected');
5903                     return true;
5904                 }
5905                 //else console.log('no new alarms');
5906             }
5907             // else console.log('cannot process alarms');
5908
5909             return false;
5910         },
5911
5912         get: function(what, callback) {
5913             $.ajax({
5914                 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
5915                 async: true,
5916                 cache: false,
5917                 xhrFields: { withCredentials: true } // required for the cookie
5918             })
5919                 .done(function(data) {
5920                     if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
5921                         NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
5922
5923                     if(typeof callback === 'function')
5924                         callback(data);
5925                 })
5926                 .fail(function() {
5927                     NETDATA.error(415, NETDATA.alarms.server);
5928
5929                     if(typeof callback === 'function')
5930                         callback(null);
5931                 });
5932         },
5933
5934         update_forever: function() {
5935             NETDATA.alarms.get('active', function(data) {
5936                 if(data !== null) {
5937                     NETDATA.alarms.current = data;
5938
5939                     if(NETDATA.alarms.check_notifications() === true) {
5940                         NETDATA.alarms.notifyAll();
5941                     }
5942
5943                     if (typeof NETDATA.alarms.callback === 'function') {
5944                         NETDATA.alarms.callback(data);
5945                     }
5946
5947                     // Health monitoring is disabled on this netdata
5948                     if(data.status === false) return;
5949                 }
5950
5951                 setTimeout(NETDATA.alarms.update_forever, 10000);
5952             });
5953         },
5954
5955         get_log: function(last_id, callback) {
5956             // console.log('fetching all log after ' + last_id.toString());
5957             $.ajax({
5958                 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
5959                 async: true,
5960                 cache: false,
5961                 xhrFields: { withCredentials: true } // required for the cookie
5962             })
5963                 .done(function(data) {
5964                     if(typeof callback === 'function')
5965                         callback(data);
5966                 })
5967                 .fail(function() {
5968                     NETDATA.error(416, NETDATA.alarms.server);
5969
5970                     if(typeof callback === 'function')
5971                         callback(null);
5972                 });
5973         },
5974
5975         init: function() {
5976             var host = NETDATA.serverDefault;
5977             while(host.slice(-1) === '/')
5978                 host = host.substring(0, host.length - 1);
5979             NETDATA.alarms.server = host;
5980
5981             NETDATA.alarms.last_notification_id = NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
5982
5983             if(NETDATA.alarms.onclick === null)
5984                 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
5985
5986             if(netdataShowAlarms === true) {
5987                 NETDATA.alarms.update_forever();
5988             
5989                 if('Notification' in window) {
5990                     // console.log('notifications available');
5991                     NETDATA.alarms.notifications = true;
5992
5993                     if(Notification.permission === 'default')
5994                         Notification.requestPermission();
5995                 }
5996             }
5997         }
5998     };
5999
6000     // ----------------------------------------------------------------------------------------------------------------
6001     // Registry of netdata hosts
6002
6003     NETDATA.registry = {
6004         server: null,       // the netdata registry server
6005         person_guid: null,  // the unique ID of this browser / user
6006         machine_guid: null, // the unique ID the netdata server that served dashboard.js
6007         hostname: null,     // the hostname of the netdata server that served dashboard.js
6008         machines: null,         // the user's other URLs
6009         machines_array: null,   // the user's other URLs in an array
6010         person_urls: null,
6011
6012         parsePersonUrls: function(person_urls) {
6013             // console.log(person_urls);
6014             NETDATA.registry.person_urls = person_urls;
6015
6016             if(person_urls) {
6017                 NETDATA.registry.machines = {};
6018                 NETDATA.registry.machines_array = new Array();
6019
6020                 var now = new Date().getTime();
6021                 var apu = person_urls;
6022                 var i = apu.length;
6023                 while(i--) {
6024                     if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6025                         // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6026
6027                         var obj = {
6028                             guid: apu[i][0],
6029                             url: apu[i][1],
6030                             last_t: apu[i][2],
6031                             accesses: apu[i][3],
6032                             name: apu[i][4],
6033                             alternate_urls: new Array()
6034                         };
6035                         obj.alternate_urls.push(apu[i][1]);
6036
6037                         NETDATA.registry.machines[apu[i][0]] = obj;
6038                         NETDATA.registry.machines_array.push(obj);
6039                     }
6040                     else {
6041                         // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6042
6043                         var pu = NETDATA.registry.machines[apu[i][0]];
6044                         if(pu.last_t < apu[i][2]) {
6045                             pu.url = apu[i][1];
6046                             pu.last_t = apu[i][2];
6047                             pu.name = apu[i][4];
6048                         }
6049                         pu.accesses += apu[i][3];
6050                         pu.alternate_urls.push(apu[i][1]);
6051                     }
6052                 }
6053             }
6054
6055             if(typeof netdataRegistryCallback === 'function')
6056                 netdataRegistryCallback(NETDATA.registry.machines_array);
6057         },
6058
6059         init: function() {
6060             if(netdataRegistry !== true) return;
6061
6062             NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6063                 if(data) {
6064                     NETDATA.registry.server = data.registry;
6065                     NETDATA.registry.machine_guid = data.machine_guid;
6066                     NETDATA.registry.hostname = data.hostname;
6067
6068                     NETDATA.registry.access(2, function (person_urls) {
6069                         NETDATA.registry.parsePersonUrls(person_urls);
6070
6071                     });
6072                 }
6073             });
6074         },
6075
6076         hello: function(host, callback) {
6077             while(host.slice(-1) === '/')
6078                 host = host.substring(0, host.length - 1);
6079
6080             // send HELLO to a netdata server:
6081             // 1. verifies the server is reachable
6082             // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6083             $.ajax({
6084                     url: host + '/api/v1/registry?action=hello',
6085                     async: true,
6086                     cache: false,
6087                     xhrFields: { withCredentials: true } // required for the cookie
6088                 })
6089                 .done(function(data) {
6090                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6091                         NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6092                         data = null;
6093                     }
6094
6095                     if(typeof callback === 'function')
6096                         callback(data);
6097                 })
6098                 .fail(function() {
6099                     NETDATA.error(407, host);
6100
6101                     if(typeof callback === 'function')
6102                         callback(null);
6103                 });
6104         },
6105
6106         access: function(max_redirects, callback) {
6107             // send ACCESS to a netdata registry:
6108             // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6109             // 2. it responds with a list of netdata servers we know
6110             // the registry identifies us using a cookie it sets the first time we access it
6111             // the registry may respond with a redirect URL to send us to another registry
6112             $.ajax({
6113                     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),
6114                     async: true,
6115                     cache: false,
6116                     xhrFields: { withCredentials: true } // required for the cookie
6117                 })
6118                 .done(function(data) {
6119                     var redirect = null;
6120                     if(typeof data.registry === 'string')
6121                         redirect = data.registry;
6122
6123                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6124                         NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6125                         data = null;
6126                     }
6127
6128                     if(data === null) {
6129                         if(redirect !== null && max_redirects > 0) {
6130                             NETDATA.registry.server = redirect;
6131                             NETDATA.registry.access(max_redirects - 1, callback);
6132                         }
6133                         else {
6134                             if(typeof callback === 'function')
6135                                 callback(null);
6136                         }
6137                     }
6138                     else {
6139                         if(typeof data.person_guid === 'string')
6140                             NETDATA.registry.person_guid = data.person_guid;
6141
6142                         if(typeof callback === 'function')
6143                             callback(data.urls);
6144                     }
6145                 })
6146                 .fail(function() {
6147                     NETDATA.error(410, NETDATA.registry.server);
6148
6149                     if(typeof callback === 'function')
6150                         callback(null);
6151                 });
6152         },
6153
6154         delete: function(delete_url, callback) {
6155             // send DELETE to a netdata registry:
6156             $.ajax({
6157                 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),
6158                 async: true,
6159                 cache: false,
6160                 xhrFields: { withCredentials: true } // required for the cookie
6161             })
6162                 .done(function(data) {
6163                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6164                         NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6165                         data = null;
6166                     }
6167
6168                     if(typeof callback === 'function')
6169                         callback(data);
6170                 })
6171                 .fail(function() {
6172                     NETDATA.error(412, NETDATA.registry.server);
6173
6174                     if(typeof callback === 'function')
6175                         callback(null);
6176                 });
6177         },
6178
6179         search: function(machine_guid, callback) {
6180             // SEARCH for the URLs of a machine:
6181             $.ajax({
6182                 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,
6183                 async: true,
6184                 cache: false,
6185                 xhrFields: { withCredentials: true } // required for the cookie
6186             })
6187                 .done(function(data) {
6188                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6189                         NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6190                         data = null;
6191                     }
6192
6193                     if(typeof callback === 'function')
6194                         callback(data);
6195                 })
6196                 .fail(function() {
6197                     NETDATA.error(418, NETDATA.registry.server);
6198
6199                     if(typeof callback === 'function')
6200                         callback(null);
6201                 });
6202         },
6203
6204         switch: function(new_person_guid, callback) {
6205             // impersonate
6206             $.ajax({
6207                 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,
6208                 async: true,
6209                 cache: false,
6210                 xhrFields: { withCredentials: true } // required for the cookie
6211             })
6212                 .done(function(data) {
6213                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6214                         NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6215                         data = null;
6216                     }
6217
6218                     if(typeof callback === 'function')
6219                         callback(data);
6220                 })
6221                 .fail(function() {
6222                     NETDATA.error(414, NETDATA.registry.server);
6223
6224                     if(typeof callback === 'function')
6225                         callback(null);
6226                 });
6227         }
6228     };
6229
6230     // ----------------------------------------------------------------------------------------------------------------
6231     // Boot it!
6232
6233     if(typeof netdataPrepCallback === 'function')
6234         netdataPrepCallback();
6235
6236     NETDATA.errorReset();
6237     NETDATA.loadRequiredCSS(0);
6238
6239     NETDATA._loadjQuery(function() {
6240         NETDATA.loadRequiredJs(0, function() {
6241             if(typeof $().emulateTransitionEnd !== 'function') {
6242                 // bootstrap is not available
6243                 NETDATA.options.current.show_help = false;
6244             }
6245
6246             if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6247                 if(NETDATA.options.debug.main_loop === true)
6248                     console.log('starting chart refresh thread');
6249
6250                 NETDATA.start();
6251             }
6252         });
6253     });
6254
6255     // window.NETDATA = NETDATA;
6256 // })(window, document);