]> arthur.barton.de Git - netdata.git/blob - web/dashboard.js
Merge https://github.com/firehol/netdata into postgres_plugin
[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-f6ec7be.js';
117     NETDATA.dygraph_smooth_js   = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter-f6ec7be.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             drawXGrid: self.data('dygraph-drawxgrid') || undefined,
4007             drawYGrid: self.data('dygraph-drawygrid') || undefined,
4008             gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4009             gridLineWidth: self.data('dygraph-gridlinewidth') || 0.4,
4010             gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4011
4012             maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4013             sigFigs: self.data('dygraph-sigfigs') || null,
4014             digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4015             valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4016
4017             highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4018             highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4019             highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4020
4021             pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4022             visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4023             axes: {
4024                 x: {
4025                     pixelsPerLabel: 50,
4026                     ticker: Dygraph.dateTicker,
4027                     axisLabelFormatter: function (d, gran) {
4028                         return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4029                     },
4030                     valueFormatter: function (ms) {
4031                         var d = new Date(ms);
4032                         return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4033                         // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4034                     }
4035                 },
4036                 y: {
4037                     pixelsPerLabel: 15,
4038                     valueFormatter: function (x) {
4039                         // we format legends with the state object
4040                         // no need to do anything here
4041                         // return (Math.round(x*100) / 100).toLocaleString();
4042                         // return state.legendFormatValue(x);
4043                         return x;
4044                     }
4045                 }
4046             },
4047             legendFormatter: function(data) {
4048                 var elements = state.element_legend_childs;
4049
4050                 // if the hidden div is not there
4051                 // we are not managing the legend
4052                 if(elements.hidden === null) return;
4053
4054                 if (typeof data.x !== 'undefined') {
4055                     state.legendSetDate(data.x);
4056                     var i = data.series.length;
4057                     while(i--) {
4058                         var series = data.series[i];
4059                         if(!series.isVisible) continue;
4060                         state.legendSetLabelValue(series.label, series.y);
4061                     }
4062                 }
4063
4064                 return '';
4065             },
4066             drawCallback: function(dygraph, is_initial) {
4067                 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4068                     state.dygraph_user_action = false;
4069
4070                     var x_range = dygraph.xAxisRange();
4071                     var after = Math.round(x_range[0]);
4072                     var before = Math.round(x_range[1]);
4073
4074                     if(NETDATA.options.debug.dygraph === true)
4075                         state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4076
4077                     if(before <= state.netdata_last && after >= state.netdata_first)
4078                         state.updateChartPanOrZoom(after, before);
4079                 }
4080             },
4081             zoomCallback: function(minDate, maxDate, yRanges) {
4082                 if(NETDATA.options.debug.dygraph === true)
4083                     state.log('dygraphZoomCallback()');
4084
4085                 state.globalSelectionSyncStop();
4086                 state.globalSelectionSyncDelay();
4087                 state.setMode('zoom');
4088
4089                 // refresh it to the greatest possible zoom level
4090                 state.dygraph_user_action = true;
4091                 state.dygraph_force_zoom = true;
4092                 state.updateChartPanOrZoom(minDate, maxDate);
4093             },
4094             highlightCallback: function(event, x, points, row, seriesName) {
4095                 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4096                     state.log('dygraphHighlightCallback()');
4097
4098                 state.pauseChart();
4099
4100                 // there is a bug in dygraph when the chart is zoomed enough
4101                 // the time it thinks is selected is wrong
4102                 // here we calculate the time t based on the row number selected
4103                 // which is ok
4104                 var t = state.data_after + row * state.data_update_every;
4105                 // console.log('row = ' + row + ', x = ' + x + ', t = ' + t + ' ' + ((t === x)?'SAME':(Math.abs(x-t)<=state.data_update_every)?'SIMILAR':'DIFFERENT') + ', rows in db: ' + state.data_points + ' visible(x) = ' + state.timeIsVisible(x) + ' visible(t) = ' + state.timeIsVisible(t) + ' r(x) = ' + state.calculateRowForTime(x) + ' r(t) = ' + state.calculateRowForTime(t) + ' range: ' + state.data_after + ' - ' + state.data_before + ' real: ' + state.data.after + ' - ' + state.data.before + ' every: ' + state.data_update_every);
4106
4107                 state.globalSelectionSync(x);
4108
4109                 // fix legend zIndex using the internal structures of dygraph legend module
4110                 // this works, but it is a hack!
4111                 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4112             },
4113             unhighlightCallback: function(event) {
4114                 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4115                     state.log('dygraphUnhighlightCallback()');
4116
4117                 state.unpauseChart();
4118                 state.globalSelectionSyncStop();
4119             },
4120             interactionModel : {
4121                 mousedown: function(event, dygraph, context) {
4122                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4123                         state.log('interactionModel.mousedown()');
4124
4125                     state.dygraph_user_action = true;
4126                     state.globalSelectionSyncStop();
4127
4128                     if(NETDATA.options.debug.dygraph === true)
4129                         state.log('dygraphMouseDown()');
4130
4131                     // Right-click should not initiate a zoom.
4132                     if(event.button && event.button === 2) return;
4133
4134                     context.initializeMouseDown(event, dygraph, context);
4135
4136                     if(event.button && event.button === 1) {
4137                         if (event.altKey || event.shiftKey) {
4138                             state.setMode('pan');
4139                             state.globalSelectionSyncDelay();
4140                             Dygraph.startPan(event, dygraph, context);
4141                         }
4142                         else {
4143                             state.setMode('zoom');
4144                             state.globalSelectionSyncDelay();
4145                             Dygraph.startZoom(event, dygraph, context);
4146                         }
4147                     }
4148                     else {
4149                         if (event.altKey || event.shiftKey) {
4150                             state.setMode('zoom');
4151                             state.globalSelectionSyncDelay();
4152                             Dygraph.startZoom(event, dygraph, context);
4153                         }
4154                         else {
4155                             state.setMode('pan');
4156                             state.globalSelectionSyncDelay();
4157                             Dygraph.startPan(event, dygraph, context);
4158                         }
4159                     }
4160                 },
4161                 mousemove: function(event, dygraph, context) {
4162                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4163                         state.log('interactionModel.mousemove()');
4164
4165                     if(context.isPanning) {
4166                         state.dygraph_user_action = true;
4167                         state.globalSelectionSyncStop();
4168                         state.globalSelectionSyncDelay();
4169                         state.setMode('pan');
4170                         Dygraph.movePan(event, dygraph, context);
4171                     }
4172                     else if(context.isZooming) {
4173                         state.dygraph_user_action = true;
4174                         state.globalSelectionSyncStop();
4175                         state.globalSelectionSyncDelay();
4176                         state.setMode('zoom');
4177                         Dygraph.moveZoom(event, dygraph, context);
4178                     }
4179                 },
4180                 mouseup: function(event, dygraph, context) {
4181                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4182                         state.log('interactionModel.mouseup()');
4183
4184                     if (context.isPanning) {
4185                         state.dygraph_user_action = true;
4186                         state.globalSelectionSyncDelay();
4187                         Dygraph.endPan(event, dygraph, context);
4188                     }
4189                     else if (context.isZooming) {
4190                         state.dygraph_user_action = true;
4191                         state.globalSelectionSyncDelay();
4192                         Dygraph.endZoom(event, dygraph, context);
4193                     }
4194                 },
4195                 click: function(event, dygraph, context) {
4196                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4197                         state.log('interactionModel.click()');
4198
4199                     event.preventDefault();
4200                 },
4201                 dblclick: function(event, dygraph, context) {
4202                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4203                         state.log('interactionModel.dblclick()');
4204                     NETDATA.resetAllCharts(state);
4205                 },
4206                 mousewheel: function(event, dygraph, context) {
4207                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4208                         state.log('interactionModel.mousewheel()');
4209
4210                     // Take the offset of a mouse event on the dygraph canvas and
4211                     // convert it to a pair of percentages from the bottom left.
4212                     // (Not top left, bottom is where the lower value is.)
4213                     function offsetToPercentage(g, offsetX, offsetY) {
4214                         // This is calculating the pixel offset of the leftmost date.
4215                         var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4216                         var yar0 = g.yAxisRange(0);
4217
4218                         // This is calculating the pixel of the higest value. (Top pixel)
4219                         var yOffset = g.toDomCoords(null, yar0[1])[1];
4220
4221                         // x y w and h are relative to the corner of the drawing area,
4222                         // so that the upper corner of the drawing area is (0, 0).
4223                         var x = offsetX - xOffset;
4224                         var y = offsetY - yOffset;
4225
4226                         // This is computing the rightmost pixel, effectively defining the
4227                         // width.
4228                         var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4229
4230                         // This is computing the lowest pixel, effectively defining the height.
4231                         var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4232
4233                         // Percentage from the left.
4234                         var xPct = w === 0 ? 0 : (x / w);
4235                         // Percentage from the top.
4236                         var yPct = h === 0 ? 0 : (y / h);
4237
4238                         // The (1-) part below changes it from "% distance down from the top"
4239                         // to "% distance up from the bottom".
4240                         return [xPct, (1-yPct)];
4241                     }
4242
4243                     // Adjusts [x, y] toward each other by zoomInPercentage%
4244                     // Split it so the left/bottom axis gets xBias/yBias of that change and
4245                     // tight/top gets (1-xBias)/(1-yBias) of that change.
4246                     //
4247                     // If a bias is missing it splits it down the middle.
4248                     function zoomRange(g, zoomInPercentage, xBias, yBias) {
4249                         xBias = xBias || 0.5;
4250                         yBias = yBias || 0.5;
4251
4252                         function adjustAxis(axis, zoomInPercentage, bias) {
4253                             var delta = axis[1] - axis[0];
4254                             var increment = delta * zoomInPercentage;
4255                             var foo = [increment * bias, increment * (1-bias)];
4256
4257                             return [ axis[0] + foo[0], axis[1] - foo[1] ];
4258                         }
4259
4260                         var yAxes = g.yAxisRanges();
4261                         var newYAxes = [];
4262                         for (var i = 0; i < yAxes.length; i++) {
4263                             newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4264                         }
4265
4266                         return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4267                     }
4268
4269                     if(event.altKey || event.shiftKey) {
4270                         state.dygraph_user_action = true;
4271
4272                         state.globalSelectionSyncStop();
4273                         state.globalSelectionSyncDelay();
4274
4275                         // http://dygraphs.com/gallery/interaction-api.js
4276                         var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4277                         var percentage = normal / 50;
4278
4279                         if (!(event.offsetX && event.offsetY)){
4280                             event.offsetX = event.layerX - event.target.offsetLeft;
4281                             event.offsetY = event.layerY - event.target.offsetTop;
4282                         }
4283
4284                         var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4285                         var xPct = percentages[0];
4286                         var yPct = percentages[1];
4287
4288                         var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4289
4290                         var after = new_x_range[0];
4291                         var before = new_x_range[1];
4292
4293                         var first = state.netdata_first + state.data_update_every;
4294                         var last = state.netdata_last + state.data_update_every;
4295
4296                         if(before > last) {
4297                             after -= (before - last);
4298                             before = last;
4299                         }
4300                         if(after < first) {
4301                             after = first;
4302                         }
4303
4304                         state.setMode('zoom');
4305                         if(state.updateChartPanOrZoom(after, before) === true)
4306                             dygraph.updateOptions({ dateWindow: [ after, before ] });
4307
4308                         event.preventDefault();
4309                     }
4310                 },
4311                 touchstart: function(event, dygraph, context) {
4312                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4313                         state.log('interactionModel.touchstart()');
4314
4315                     state.dygraph_user_action = true;
4316                     state.setMode('zoom');
4317                     state.pauseChart();
4318
4319                     Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4320
4321                     // we overwrite the touch directions at the end, to overwrite
4322                     // the internal default of dygraphs
4323                     context.touchDirections = { x: true, y: false };
4324
4325                     state.dygraph_last_touch_start = new Date().getTime();
4326                     state.dygraph_last_touch_move = 0;
4327
4328                     if(typeof event.touches[0].pageX === 'number')
4329                         state.dygraph_last_touch_page_x = event.touches[0].pageX;
4330                     else
4331                         state.dygraph_last_touch_page_x = 0;
4332                 },
4333                 touchmove: function(event, dygraph, context) {
4334                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4335                         state.log('interactionModel.touchmove()');
4336
4337                     state.dygraph_user_action = true;
4338                     Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4339
4340                     state.dygraph_last_touch_move = new Date().getTime();
4341                 },
4342                 touchend: function(event, dygraph, context) {
4343                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4344                         state.log('interactionModel.touchend()');
4345
4346                     state.dygraph_user_action = true;
4347                     Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4348
4349                     // if it didn't move, it is a selection
4350                     if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4351                         // internal api of dygraphs
4352                         var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4353                         var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4354                         if(NETDATA.dygraphSetSelection(state, t) === true)
4355                             state.globalSelectionSync(t);
4356                     }
4357
4358                     // if it was double tap within double click time, reset the charts
4359                     var now = new Date().getTime();
4360                     if(typeof state.dygraph_last_touch_end !== 'undefined') {
4361                         if(state.dygraph_last_touch_move === 0) {
4362                             var dt = now - state.dygraph_last_touch_end;
4363                             if(dt <= NETDATA.options.current.double_click_speed)
4364                                 NETDATA.resetAllCharts(state);
4365                         }
4366                     }
4367
4368                     // remember the timestamp of the last touch end
4369                     state.dygraph_last_touch_end = now;
4370                 }
4371             }
4372         };
4373
4374         if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4375             state.dygraph_options.drawGrid = false;
4376             state.dygraph_options.drawAxis = false;
4377             state.dygraph_options.title = undefined;
4378             state.dygraph_options.units = undefined;
4379             state.dygraph_options.ylabel = undefined;
4380             state.dygraph_options.yLabelWidth = 0;
4381             state.dygraph_options.labelsDivWidth = 120;
4382             state.dygraph_options.labelsDivStyles.width = '120px';
4383             state.dygraph_options.labelsSeparateLines = true;
4384             state.dygraph_options.rightGap = 0;
4385             state.dygraph_options.yRangePad = 1;
4386         }
4387
4388         if(smooth === true) {
4389             state.dygraph_smooth_eligible = true;
4390
4391             if(NETDATA.options.current.smooth_plot === true)
4392                 state.dygraph_options.plotter = smoothPlotter;
4393         }
4394         else state.dygraph_smooth_eligible = false;
4395
4396         state.dygraph_instance = new Dygraph(state.element_chart,
4397             data.result.data, state.dygraph_options);
4398
4399         state.dygraph_force_zoom = false;
4400         state.dygraph_user_action = false;
4401         state.dygraph_last_rendered = new Date().getTime();
4402         return true;
4403     };
4404
4405     // ----------------------------------------------------------------------------------------------------------------
4406     // morris
4407
4408     NETDATA.morrisInitialize = function(callback) {
4409         if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4410
4411             // morris requires raphael
4412             if(!NETDATA.chartLibraries.raphael.initialized) {
4413                 if(NETDATA.chartLibraries.raphael.enabled) {
4414                     NETDATA.raphaelInitialize(function() {
4415                         NETDATA.morrisInitialize(callback);
4416                     });
4417                 }
4418                 else {
4419                     NETDATA.chartLibraries.morris.enabled = false;
4420                     if(typeof callback === "function")
4421                         callback();
4422                 }
4423             }
4424             else {
4425                 NETDATA._loadCSS(NETDATA.morris_css);
4426
4427                 $.ajax({
4428                     url: NETDATA.morris_js,
4429                     cache: true,
4430                     dataType: "script",
4431                     xhrFields: { withCredentials: true } // required for the cookie
4432                 })
4433                 .done(function() {
4434                     NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4435                 })
4436                 .fail(function() {
4437                     NETDATA.chartLibraries.morris.enabled = false;
4438                     NETDATA.error(100, NETDATA.morris_js);
4439                 })
4440                 .always(function() {
4441                     if(typeof callback === "function")
4442                         callback();
4443                 });
4444             }
4445         }
4446         else {
4447             NETDATA.chartLibraries.morris.enabled = false;
4448             if(typeof callback === "function")
4449                 callback();
4450         }
4451     };
4452
4453     NETDATA.morrisChartUpdate = function(state, data) {
4454         state.morris_instance.setData(data.result.data);
4455         return true;
4456     };
4457
4458     NETDATA.morrisChartCreate = function(state, data) {
4459
4460         state.morris_options = {
4461                 element: state.element_chart.id,
4462                 data: data.result.data,
4463                 xkey: 'time',
4464                 ykeys: data.dimension_names,
4465                 labels: data.dimension_names,
4466                 lineWidth: 2,
4467                 pointSize: 3,
4468                 smooth: true,
4469                 hideHover: 'auto',
4470                 parseTime: true,
4471                 continuousLine: false,
4472                 behaveLikeLine: false
4473         };
4474
4475         if(state.chart.chart_type === 'line')
4476             state.morris_instance = new Morris.Line(state.morris_options);
4477
4478         else if(state.chart.chart_type === 'area') {
4479             state.morris_options.behaveLikeLine = true;
4480             state.morris_instance = new Morris.Area(state.morris_options);
4481         }
4482         else // stacked
4483             state.morris_instance = new Morris.Area(state.morris_options);
4484
4485         return true;
4486     };
4487
4488     // ----------------------------------------------------------------------------------------------------------------
4489     // raphael
4490
4491     NETDATA.raphaelInitialize = function(callback) {
4492         if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4493             $.ajax({
4494                 url: NETDATA.raphael_js,
4495                 cache: true,
4496                 dataType: "script",
4497                 xhrFields: { withCredentials: true } // required for the cookie
4498             })
4499             .done(function() {
4500                 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4501             })
4502             .fail(function() {
4503                 NETDATA.chartLibraries.raphael.enabled = false;
4504                 NETDATA.error(100, NETDATA.raphael_js);
4505             })
4506             .always(function() {
4507                 if(typeof callback === "function")
4508                     callback();
4509             });
4510         }
4511         else {
4512             NETDATA.chartLibraries.raphael.enabled = false;
4513             if(typeof callback === "function")
4514                 callback();
4515         }
4516     };
4517
4518     NETDATA.raphaelChartUpdate = function(state, data) {
4519         $(state.element_chart).raphael(data.result, {
4520             width: state.chartWidth(),
4521             height: state.chartHeight()
4522         });
4523
4524         return false;
4525     };
4526
4527     NETDATA.raphaelChartCreate = function(state, data) {
4528         $(state.element_chart).raphael(data.result, {
4529             width: state.chartWidth(),
4530             height: state.chartHeight()
4531         });
4532
4533         return false;
4534     };
4535
4536     // ----------------------------------------------------------------------------------------------------------------
4537     // C3
4538
4539     NETDATA.c3Initialize = function(callback) {
4540         if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4541
4542             // C3 requires D3
4543             if(!NETDATA.chartLibraries.d3.initialized) {
4544                 if(NETDATA.chartLibraries.d3.enabled) {
4545                     NETDATA.d3Initialize(function() {
4546                         NETDATA.c3Initialize(callback);
4547                     });
4548                 }
4549                 else {
4550                     NETDATA.chartLibraries.c3.enabled = false;
4551                     if(typeof callback === "function")
4552                         callback();
4553                 }
4554             }
4555             else {
4556                 NETDATA._loadCSS(NETDATA.c3_css);
4557
4558                 $.ajax({
4559                     url: NETDATA.c3_js,
4560                     cache: true,
4561                     dataType: "script",
4562                     xhrFields: { withCredentials: true } // required for the cookie
4563                 })
4564                 .done(function() {
4565                     NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4566                 })
4567                 .fail(function() {
4568                     NETDATA.chartLibraries.c3.enabled = false;
4569                     NETDATA.error(100, NETDATA.c3_js);
4570                 })
4571                 .always(function() {
4572                     if(typeof callback === "function")
4573                         callback();
4574                 });
4575             }
4576         }
4577         else {
4578             NETDATA.chartLibraries.c3.enabled = false;
4579             if(typeof callback === "function")
4580                 callback();
4581         }
4582     };
4583
4584     NETDATA.c3ChartUpdate = function(state, data) {
4585         state.c3_instance.destroy();
4586         return NETDATA.c3ChartCreate(state, data);
4587
4588         //state.c3_instance.load({
4589         //  rows: data.result,
4590         //  unload: true
4591         //});
4592
4593         //return true;
4594     };
4595
4596     NETDATA.c3ChartCreate = function(state, data) {
4597
4598         state.element_chart.id = 'c3-' + state.uuid;
4599         // console.log('id = ' + state.element_chart.id);
4600
4601         state.c3_instance = c3.generate({
4602             bindto: '#' + state.element_chart.id,
4603             size: {
4604                 width: state.chartWidth(),
4605                 height: state.chartHeight()
4606             },
4607             color: {
4608                 pattern: state.chartColors()
4609             },
4610             data: {
4611                 x: 'time',
4612                 rows: data.result,
4613                 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4614             },
4615             axis: {
4616                 x: {
4617                     type: 'timeseries',
4618                     tick: {
4619                         format: function(x) {
4620                             return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4621                         }
4622                     }
4623                 }
4624             },
4625             grid: {
4626                 x: {
4627                     show: true
4628                 },
4629                 y: {
4630                     show: true
4631                 }
4632             },
4633             point: {
4634                 show: false
4635             },
4636             line: {
4637                 connectNull: false
4638             },
4639             transition: {
4640                 duration: 0
4641             },
4642             interaction: {
4643                 enabled: true
4644             }
4645         });
4646
4647         // console.log(state.c3_instance);
4648
4649         return true;
4650     };
4651
4652     // ----------------------------------------------------------------------------------------------------------------
4653     // D3
4654
4655     NETDATA.d3Initialize = function(callback) {
4656         if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4657             $.ajax({
4658                 url: NETDATA.d3_js,
4659                 cache: true,
4660                 dataType: "script",
4661                 xhrFields: { withCredentials: true } // required for the cookie
4662             })
4663             .done(function() {
4664                 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4665             })
4666             .fail(function() {
4667                 NETDATA.chartLibraries.d3.enabled = false;
4668                 NETDATA.error(100, NETDATA.d3_js);
4669             })
4670             .always(function() {
4671                 if(typeof callback === "function")
4672                     callback();
4673             });
4674         }
4675         else {
4676             NETDATA.chartLibraries.d3.enabled = false;
4677             if(typeof callback === "function")
4678                 callback();
4679         }
4680     };
4681
4682     NETDATA.d3ChartUpdate = function(state, data) {
4683         return false;
4684     };
4685
4686     NETDATA.d3ChartCreate = function(state, data) {
4687         return false;
4688     };
4689
4690     // ----------------------------------------------------------------------------------------------------------------
4691     // google charts
4692
4693     NETDATA.googleInitialize = function(callback) {
4694         if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4695             $.ajax({
4696                 url: NETDATA.google_js,
4697                 cache: true,
4698                 dataType: "script",
4699                 xhrFields: { withCredentials: true } // required for the cookie
4700             })
4701             .done(function() {
4702                 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4703                 google.load('visualization', '1.1', {
4704                     'packages': ['corechart', 'controls'],
4705                     'callback': callback
4706                 });
4707             })
4708             .fail(function() {
4709                 NETDATA.chartLibraries.google.enabled = false;
4710                 NETDATA.error(100, NETDATA.google_js);
4711                 if(typeof callback === "function")
4712                     callback();
4713             });
4714         }
4715         else {
4716             NETDATA.chartLibraries.google.enabled = false;
4717             if(typeof callback === "function")
4718                 callback();
4719         }
4720     };
4721
4722     NETDATA.googleChartUpdate = function(state, data) {
4723         var datatable = new google.visualization.DataTable(data.result);
4724         state.google_instance.draw(datatable, state.google_options);
4725         return true;
4726     };
4727
4728     NETDATA.googleChartCreate = function(state, data) {
4729         var datatable = new google.visualization.DataTable(data.result);
4730
4731         state.google_options = {
4732             colors: state.chartColors(),
4733
4734             // do not set width, height - the chart resizes itself
4735             //width: state.chartWidth(),
4736             //height: state.chartHeight(),
4737             lineWidth: 1,
4738             title: state.title,
4739             fontSize: 11,
4740             hAxis: {
4741             //  title: "Time of Day",
4742             //  format:'HH:mm:ss',
4743                 viewWindowMode: 'maximized',
4744                 slantedText: false,
4745                 format:'HH:mm:ss',
4746                 textStyle: {
4747                     fontSize: 9
4748                 },
4749                 gridlines: {
4750                     color: '#EEE'
4751                 }
4752             },
4753             vAxis: {
4754                 title: state.units,
4755                 viewWindowMode: 'pretty',
4756                 minValue: -0.1,
4757                 maxValue: 0.1,
4758                 direction: 1,
4759                 textStyle: {
4760                     fontSize: 9
4761                 },
4762                 gridlines: {
4763                     color: '#EEE'
4764                 }
4765             },
4766             chartArea: {
4767                 width: '65%',
4768                 height: '80%'
4769             },
4770             focusTarget: 'category',
4771             annotation: {
4772                 '1': {
4773                     style: 'line'
4774                 }
4775             },
4776             pointsVisible: 0,
4777             titlePosition: 'out',
4778             titleTextStyle: {
4779                 fontSize: 11
4780             },
4781             tooltip: {
4782                 isHtml: false,
4783                 ignoreBounds: true,
4784                 textStyle: {
4785                     fontSize: 9
4786                 }
4787             },
4788             curveType: 'function',
4789             areaOpacity: 0.3,
4790             isStacked: false
4791         };
4792
4793         switch(state.chart.chart_type) {
4794             case "area":
4795                 state.google_options.vAxis.viewWindowMode = 'maximized';
4796                 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4797                 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4798                 break;
4799
4800             case "stacked":
4801                 state.google_options.isStacked = true;
4802                 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4803                 state.google_options.vAxis.viewWindowMode = 'maximized';
4804                 state.google_options.vAxis.minValue = null;
4805                 state.google_options.vAxis.maxValue = null;
4806                 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4807                 break;
4808
4809             default:
4810             case "line":
4811                 state.google_options.lineWidth = 2;
4812                 state.google_instance = new google.visualization.LineChart(state.element_chart);
4813                 break;
4814         }
4815
4816         state.google_instance.draw(datatable, state.google_options);
4817         return true;
4818     };
4819
4820     // ----------------------------------------------------------------------------------------------------------------
4821
4822     NETDATA.percentFromValueMax = function(value, max) {
4823         if(value === null) value = 0;
4824         if(max < value) max = value;
4825
4826         var pcent = 0;
4827         if(max !== 0) {
4828             pcent = Math.round(value * 100 / max);
4829             if(pcent === 0 && value > 0) pcent = 1;
4830         }
4831
4832         return pcent;
4833     };
4834
4835     // ----------------------------------------------------------------------------------------------------------------
4836     // easy-pie-chart
4837
4838     NETDATA.easypiechartInitialize = function(callback) {
4839         if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4840             $.ajax({
4841                 url: NETDATA.easypiechart_js,
4842                 cache: true,
4843                 dataType: "script",
4844                 xhrFields: { withCredentials: true } // required for the cookie
4845             })
4846                 .done(function() {
4847                     NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4848                 })
4849                 .fail(function() {
4850                     NETDATA.chartLibraries.easypiechart.enabled = false;
4851                     NETDATA.error(100, NETDATA.easypiechart_js);
4852                 })
4853                 .always(function() {
4854                     if(typeof callback === "function")
4855                         callback();
4856                 })
4857         }
4858         else {
4859             NETDATA.chartLibraries.easypiechart.enabled = false;
4860             if(typeof callback === "function")
4861                 callback();
4862         }
4863     };
4864
4865     NETDATA.easypiechartClearSelection = function(state) {
4866         if(typeof state.easyPieChartEvent !== 'undefined') {
4867             if(state.easyPieChartEvent.timer !== null)
4868                 clearTimeout(state.easyPieChartEvent.timer);
4869
4870             state.easyPieChartEvent.timer = null;
4871         }
4872
4873         if(state.isAutoRefreshable() === true && state.data !== null) {
4874             NETDATA.easypiechartChartUpdate(state, state.data);
4875         }
4876         else {
4877             state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4878             state.easyPieChart_instance.update(0);
4879         }
4880         state.easyPieChart_instance.enableAnimation();
4881
4882         return true;
4883     };
4884
4885     NETDATA.easypiechartSetSelection = function(state, t) {
4886         if(state.timeIsVisible(t) !== true)
4887             return NETDATA.easypiechartClearSelection(state);
4888
4889         var slot = state.calculateRowForTime(t);
4890         if(slot < 0 || slot >= state.data.result.length)
4891             return NETDATA.easypiechartClearSelection(state);
4892
4893         if(typeof state.easyPieChartEvent === 'undefined') {
4894             state.easyPieChartEvent = {
4895                 timer: null,
4896                 value: 0,
4897                 pcent: 0
4898             };
4899         }
4900
4901         var value = state.data.result[state.data.result.length - 1 - slot];
4902         var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4903         var pcent = NETDATA.percentFromValueMax(value, max);
4904
4905         state.easyPieChartEvent.value = value;
4906         state.easyPieChartEvent.pcent = pcent;
4907         state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4908
4909         if(state.easyPieChartEvent.timer === null) {
4910             state.easyPieChart_instance.disableAnimation();
4911
4912             state.easyPieChartEvent.timer = setTimeout(function() {
4913                 state.easyPieChartEvent.timer = null;
4914                 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4915             }, NETDATA.options.current.charts_selection_animation_delay);
4916         }
4917
4918         return true;
4919     };
4920
4921     NETDATA.easypiechartChartUpdate = function(state, data) {
4922         var value, max, pcent;
4923
4924         if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
4925             value = null;
4926             max = 0;
4927             pcent = 0;
4928         }
4929         else {
4930             value = data.result[0];
4931             max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4932             pcent = NETDATA.percentFromValueMax(value, max);
4933         }
4934
4935         state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4936         state.easyPieChart_instance.update(pcent);
4937         return true;
4938     };
4939
4940     NETDATA.easypiechartChartCreate = function(state, data) {
4941         var self = $(state.element);
4942         var chart = $(state.element_chart);
4943
4944         var value = data.result[0];
4945         var max = self.data('easypiechart-max-value') || null;
4946         var adjust = self.data('easypiechart-adjust') || null;
4947
4948         if(max === null) {
4949             max = data.max;
4950             state.easyPieChartMax = null;
4951         }
4952         else
4953             state.easyPieChartMax = max;
4954
4955         var pcent = NETDATA.percentFromValueMax(value, max);
4956
4957         chart.data('data-percent', pcent);
4958
4959         var size;
4960         switch(adjust) {
4961             case 'width': size = state.chartHeight(); break;
4962             case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4963             case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4964             case 'height':
4965             default: size = state.chartWidth(); break;
4966         }
4967         state.element.style.width = size + 'px';
4968         state.element.style.height = size + 'px';
4969
4970         var stroke = Math.floor(size / 22);
4971         if(stroke < 3) stroke = 2;
4972
4973         var valuefontsize = Math.floor((size * 2 / 3) / 5);
4974         var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4975         state.easyPieChartLabel = document.createElement('span');
4976         state.easyPieChartLabel.className = 'easyPieChartLabel';
4977         state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4978         state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4979         state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4980         state.element_chart.appendChild(state.easyPieChartLabel);
4981
4982         var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4983         var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4984         state.easyPieChartTitle = document.createElement('span');
4985         state.easyPieChartTitle.className = 'easyPieChartTitle';
4986         state.easyPieChartTitle.innerHTML = state.title;
4987         state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4988         state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4989         state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4990         state.element_chart.appendChild(state.easyPieChartTitle);
4991
4992         var unitfontsize = Math.round(titlefontsize * 0.9);
4993         var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4994         state.easyPieChartUnits = document.createElement('span');
4995         state.easyPieChartUnits.className = 'easyPieChartUnits';
4996         state.easyPieChartUnits.innerHTML = state.units;
4997         state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4998         state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4999         state.element_chart.appendChild(state.easyPieChartUnits);
5000
5001         chart.easyPieChart({
5002             barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
5003             trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5004             scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5005             scaleLength: self.data('easypiechart-scalelength') || 5,
5006             lineCap: self.data('easypiechart-linecap') || 'round',
5007             lineWidth: self.data('easypiechart-linewidth') || stroke,
5008             trackWidth: self.data('easypiechart-trackwidth') || undefined,
5009             size: self.data('easypiechart-size') || size,
5010             rotate: self.data('easypiechart-rotate') || 0,
5011             animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
5012             easing: self.data('easypiechart-easing') || undefined
5013         });
5014
5015         // when we just re-create the chart
5016         // do not animate the first update
5017         var animate = true;
5018         if(typeof state.easyPieChart_instance !== 'undefined')
5019             animate = false;
5020
5021         state.easyPieChart_instance = chart.data('easyPieChart');
5022         if(animate === false) state.easyPieChart_instance.disableAnimation();
5023         state.easyPieChart_instance.update(pcent);
5024         if(animate === false) state.easyPieChart_instance.enableAnimation();
5025         return true;
5026     };
5027
5028     // ----------------------------------------------------------------------------------------------------------------
5029     // gauge.js
5030
5031     NETDATA.gaugeInitialize = function(callback) {
5032         if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5033             $.ajax({
5034                 url: NETDATA.gauge_js,
5035                 cache: true,
5036                 dataType: "script",
5037                 xhrFields: { withCredentials: true } // required for the cookie
5038             })
5039                 .done(function() {
5040                     NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5041                 })
5042                 .fail(function() {
5043                     NETDATA.chartLibraries.gauge.enabled = false;
5044                     NETDATA.error(100, NETDATA.gauge_js);
5045                 })
5046                 .always(function() {
5047                     if(typeof callback === "function")
5048                         callback();
5049                 })
5050         }
5051         else {
5052             NETDATA.chartLibraries.gauge.enabled = false;
5053             if(typeof callback === "function")
5054                 callback();
5055         }
5056     };
5057
5058     NETDATA.gaugeAnimation = function(state, status) {
5059         var speed = 32;
5060
5061         if(typeof status === 'boolean' && status === false)
5062             speed = 1000000000;
5063         else if(typeof status === 'number')
5064             speed = status;
5065
5066         // console.log('gauge speed ' + speed);
5067         state.gauge_instance.animationSpeed = speed;
5068         state.___gaugeOld__.speed = speed;
5069     };
5070
5071     NETDATA.gaugeSet = function(state, value, min, max) {
5072         if(typeof value !== 'number') value = 0;
5073         if(typeof min !== 'number') min = 0;
5074         if(typeof max !== 'number') max = 0;
5075         if(value > max) max = value;
5076         if(value < min) min = value;
5077         if(min > max) {
5078             var t = min;
5079             min = max;
5080             max = t;
5081         }
5082         else if(min == max)
5083             max = min + 1;
5084
5085         // gauge.js has an issue if the needle
5086         // is smaller than min or larger than max
5087         // when we set the new values
5088         // the needle will go crazy
5089
5090         // to prevent it, we always feed it
5091         // with a percentage, so that the needle
5092         // is always between min and max
5093         var pcent = (value - min) * 100 / (max - min);
5094
5095         // these should never happen
5096         if(pcent < 0) pcent = 0;
5097         if(pcent > 100) pcent = 100;
5098
5099         state.gauge_instance.set(pcent);
5100         // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5101
5102         state.___gaugeOld__.value = value;
5103         state.___gaugeOld__.min = min;
5104         state.___gaugeOld__.max = max;
5105     };
5106
5107     NETDATA.gaugeSetLabels = function(state, value, min, max) {
5108         if(state.___gaugeOld__.valueLabel !== value) {
5109             state.___gaugeOld__.valueLabel = value;
5110             state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
5111         }
5112         if(state.___gaugeOld__.minLabel !== min) {
5113             state.___gaugeOld__.minLabel = min;
5114             state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
5115         }
5116         if(state.___gaugeOld__.maxLabel !== max) {
5117             state.___gaugeOld__.maxLabel = max;
5118             state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
5119         }
5120     };
5121
5122     NETDATA.gaugeClearSelection = function(state) {
5123         if(typeof state.gaugeEvent !== 'undefined') {
5124             if(state.gaugeEvent.timer !== null)
5125                 clearTimeout(state.gaugeEvent.timer);
5126
5127             state.gaugeEvent.timer = null;
5128         }
5129
5130         if(state.isAutoRefreshable() === true && state.data !== null) {
5131             NETDATA.gaugeChartUpdate(state, state.data);
5132         }
5133         else {
5134             NETDATA.gaugeAnimation(state, false);
5135             NETDATA.gaugeSet(state, null, null, null);
5136             NETDATA.gaugeSetLabels(state, null, null, null);
5137         }
5138
5139         NETDATA.gaugeAnimation(state, true);
5140         return true;
5141     };
5142
5143     NETDATA.gaugeSetSelection = function(state, t) {
5144         if(state.timeIsVisible(t) !== true)
5145             return NETDATA.gaugeClearSelection(state);
5146
5147         var slot = state.calculateRowForTime(t);
5148         if(slot < 0 || slot >= state.data.result.length)
5149             return NETDATA.gaugeClearSelection(state);
5150
5151         if(typeof state.gaugeEvent === 'undefined') {
5152             state.gaugeEvent = {
5153                 timer: null,
5154                 value: 0,
5155                 min: 0,
5156                 max: 0
5157             };
5158         }
5159
5160         var value = state.data.result[state.data.result.length - 1 - slot];
5161         var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
5162         var min = 0;
5163
5164         state.gaugeEvent.value = value;
5165         state.gaugeEvent.max = max;
5166         state.gaugeEvent.min = min;
5167         NETDATA.gaugeSetLabels(state, value, min, max);
5168
5169         if(state.gaugeEvent.timer === null) {
5170             NETDATA.gaugeAnimation(state, false);
5171
5172             state.gaugeEvent.timer = setTimeout(function() {
5173                 state.gaugeEvent.timer = null;
5174                 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5175             }, NETDATA.options.current.charts_selection_animation_delay);
5176         }
5177
5178         return true;
5179     };
5180
5181     NETDATA.gaugeChartUpdate = function(state, data) {
5182         var value, min, max;
5183
5184         if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5185             value = 0;
5186             min = 0;
5187             max = 1;
5188             NETDATA.gaugeSetLabels(state, null, null, null);
5189         }
5190         else {
5191             value = data.result[0];
5192             min = 0;
5193             max = (state.gaugeMax === null)?data.max:state.gaugeMax;
5194             if(value > max) max = value;
5195             NETDATA.gaugeSetLabels(state, value, min, max);
5196         }
5197
5198         NETDATA.gaugeSet(state, value, min, max);
5199         return true;
5200     };
5201
5202     NETDATA.gaugeChartCreate = function(state, data) {
5203         var self = $(state.element);
5204         // var chart = $(state.element_chart);
5205
5206         var value = data.result[0];
5207         var max = self.data('gauge-max-value') || null;
5208         var adjust = self.data('gauge-adjust') || null;
5209         var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5210         var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5211         var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5212         var stopColor = self.data('gauge-stop-color') || void 0;
5213         var generateGradient = self.data('gauge-generate-gradient') || false;
5214
5215         if(max === null) {
5216             max = data.max;
5217             state.gaugeMax = null;
5218         }
5219         else
5220             state.gaugeMax = max;
5221
5222         var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5223         //switch(adjust) {
5224         //  case 'width': width = height * ratio; break;
5225         //  case 'height':
5226         //  default: height = width / ratio; break;
5227         //}
5228         //state.element.style.width = width.toString() + 'px';
5229         //state.element.style.height = height.toString() + 'px';
5230
5231         var lum_d = 0.05;
5232
5233         var options = {
5234             lines: 12,                  // The number of lines to draw
5235             angle: 0.15,                // The length of each line
5236             lineWidth: 0.44,            // 0.44 The line thickness
5237             pointer: {
5238                 length: 0.8,            // 0.9 The radius of the inner circle
5239                 strokeWidth: 0.035,     // The rotation offset
5240                 color: pointerColor     // Fill color
5241             },
5242             colorStart: startColor,     // Colors
5243             colorStop: stopColor,       // just experiment with them
5244             strokeColor: strokeColor,   // to see which ones work best for you
5245             limitMax: true,
5246             generateGradient: (generateGradient === true)?true:false,
5247             gradientType: 0
5248         };
5249
5250         if (generateGradient.constructor === Array) {
5251             // example options:
5252             // data-gauge-generate-gradient="[0, 50, 100]"
5253             // data-gauge-gradient-percent-color-0="#FFFFFF"
5254             // data-gauge-gradient-percent-color-50="#999900"
5255             // data-gauge-gradient-percent-color-100="#000000"
5256
5257             options.percentColors = new Array();
5258             var len = generateGradient.length;
5259             while(len--) {
5260                 var pcent = generateGradient[len];
5261                 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5262                 if(color !== false) {
5263                     var a = new Array();
5264                     a[0] = pcent / 100;
5265                     a[1] = color;
5266                     options.percentColors.unshift(a);
5267                 }
5268             }
5269             if(options.percentColors.length === 0)
5270                 delete options.percentColors;
5271         }
5272         else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5273             options.percentColors = [
5274                 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5275                 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5276                 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5277                 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5278                 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5279                 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5280                 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5281                 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5282                 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5283                 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5284                 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5285         }
5286
5287         state.gauge_canvas = document.createElement('canvas');
5288         state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5289         state.gauge_canvas.className = 'gaugeChart';
5290         state.gauge_canvas.width  = width;
5291         state.gauge_canvas.height = height;
5292         state.element_chart.appendChild(state.gauge_canvas);
5293
5294         var valuefontsize = Math.floor(height / 6);
5295         var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5296         state.gaugeChartLabel = document.createElement('span');
5297         state.gaugeChartLabel.className = 'gaugeChartLabel';
5298         state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5299         state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5300         state.element_chart.appendChild(state.gaugeChartLabel);
5301
5302         var titlefontsize = Math.round(valuefontsize / 2);
5303         var titletop = 0;
5304         state.gaugeChartTitle = document.createElement('span');
5305         state.gaugeChartTitle.className = 'gaugeChartTitle';
5306         state.gaugeChartTitle.innerHTML = state.title;
5307         state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5308         state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5309         state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5310         state.element_chart.appendChild(state.gaugeChartTitle);
5311
5312         var unitfontsize = Math.round(titlefontsize * 0.9);
5313         state.gaugeChartUnits = document.createElement('span');
5314         state.gaugeChartUnits.className = 'gaugeChartUnits';
5315         state.gaugeChartUnits.innerHTML = state.units;
5316         state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5317         state.element_chart.appendChild(state.gaugeChartUnits);
5318
5319         state.gaugeChartMin = document.createElement('span');
5320         state.gaugeChartMin.className = 'gaugeChartMin';
5321         state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5322         state.element_chart.appendChild(state.gaugeChartMin);
5323
5324         state.gaugeChartMax = document.createElement('span');
5325         state.gaugeChartMax.className = 'gaugeChartMax';
5326         state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5327         state.element_chart.appendChild(state.gaugeChartMax);
5328
5329         // when we just re-create the chart
5330         // do not animate the first update
5331         var animate = true;
5332         if(typeof state.gauge_instance !== 'undefined')
5333             animate = false;
5334
5335         state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5336
5337         state.___gaugeOld__ = {
5338             value: value,
5339             min: 0,
5340             max: max,
5341             valueLabel: null,
5342             minLabel: null,
5343             maxLabel: null
5344         };
5345
5346         // we will always feed a percentage
5347         state.gauge_instance.minValue = 0;
5348         state.gauge_instance.maxValue = 100;
5349
5350         NETDATA.gaugeAnimation(state, animate);
5351         NETDATA.gaugeSet(state, value, 0, max);
5352         NETDATA.gaugeSetLabels(state, value, 0, max);
5353         NETDATA.gaugeAnimation(state, true);
5354         return true;
5355     };
5356
5357     // ----------------------------------------------------------------------------------------------------------------
5358     // Charts Libraries Registration
5359
5360     NETDATA.chartLibraries = {
5361         "dygraph": {
5362             initialize: NETDATA.dygraphInitialize,
5363             create: NETDATA.dygraphChartCreate,
5364             update: NETDATA.dygraphChartUpdate,
5365             resize: function(state) {
5366                 if(typeof state.dygraph_instance.resize === 'function')
5367                     state.dygraph_instance.resize();
5368             },
5369             setSelection: NETDATA.dygraphSetSelection,
5370             clearSelection:  NETDATA.dygraphClearSelection,
5371             toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5372             initialized: false,
5373             enabled: true,
5374             format: function(state) { return 'json'; },
5375             options: function(state) { return 'ms|flip'; },
5376             legend: function(state) {
5377                 if(this.isSparkline(state) === false)
5378                     return 'right-side';
5379                 else
5380                     return null;
5381             },
5382             autoresize: function(state) { return true; },
5383             max_updates_to_recreate: function(state) { return 5000; },
5384             track_colors: function(state) { return true; },
5385             pixels_per_point: function(state) {
5386                 if(this.isSparkline(state) === false)
5387                     return 3;
5388                 else
5389                     return 2;
5390             },
5391
5392             isSparkline: function(state) {
5393                 if(typeof state.dygraph_sparkline === 'undefined') {
5394                     var t = $(state.element).data('dygraph-theme');
5395                     if(t === 'sparkline')
5396                         state.dygraph_sparkline = true;
5397                     else
5398                         state.dygraph_sparkline = false;
5399                 }
5400                 return state.dygraph_sparkline;
5401             }
5402         },
5403         "sparkline": {
5404             initialize: NETDATA.sparklineInitialize,
5405             create: NETDATA.sparklineChartCreate,
5406             update: NETDATA.sparklineChartUpdate,
5407             resize: null,
5408             setSelection: undefined, // function(state, t) { return true; },
5409             clearSelection: undefined, // function(state) { return true; },
5410             toolboxPanAndZoom: null,
5411             initialized: false,
5412             enabled: true,
5413             format: function(state) { return 'array'; },
5414             options: function(state) { return 'flip|abs'; },
5415             legend: function(state) { return null; },
5416             autoresize: function(state) { return false; },
5417             max_updates_to_recreate: function(state) { return 5000; },
5418             track_colors: function(state) { return false; },
5419             pixels_per_point: function(state) { return 3; }
5420         },
5421         "peity": {
5422             initialize: NETDATA.peityInitialize,
5423             create: NETDATA.peityChartCreate,
5424             update: NETDATA.peityChartUpdate,
5425             resize: null,
5426             setSelection: undefined, // function(state, t) { return true; },
5427             clearSelection: undefined, // function(state) { return true; },
5428             toolboxPanAndZoom: null,
5429             initialized: false,
5430             enabled: true,
5431             format: function(state) { return 'ssvcomma'; },
5432             options: function(state) { return 'null2zero|flip|abs'; },
5433             legend: function(state) { return null; },
5434             autoresize: function(state) { return false; },
5435             max_updates_to_recreate: function(state) { return 5000; },
5436             track_colors: function(state) { return false; },
5437             pixels_per_point: function(state) { return 3; }
5438         },
5439         "morris": {
5440             initialize: NETDATA.morrisInitialize,
5441             create: NETDATA.morrisChartCreate,
5442             update: NETDATA.morrisChartUpdate,
5443             resize: null,
5444             setSelection: undefined, // function(state, t) { return true; },
5445             clearSelection: undefined, // function(state) { return true; },
5446             toolboxPanAndZoom: null,
5447             initialized: false,
5448             enabled: true,
5449             format: function(state) { return 'json'; },
5450             options: function(state) { return 'objectrows|ms'; },
5451             legend: function(state) { return null; },
5452             autoresize: function(state) { return false; },
5453             max_updates_to_recreate: function(state) { return 50; },
5454             track_colors: function(state) { return false; },
5455             pixels_per_point: function(state) { return 15; }
5456         },
5457         "google": {
5458             initialize: NETDATA.googleInitialize,
5459             create: NETDATA.googleChartCreate,
5460             update: NETDATA.googleChartUpdate,
5461             resize: null,
5462             setSelection: undefined, //function(state, t) { return true; },
5463             clearSelection: undefined, //function(state) { return true; },
5464             toolboxPanAndZoom: null,
5465             initialized: false,
5466             enabled: true,
5467             format: function(state) { return 'datatable'; },
5468             options: function(state) { return ''; },
5469             legend: function(state) { return null; },
5470             autoresize: function(state) { return false; },
5471             max_updates_to_recreate: function(state) { return 300; },
5472             track_colors: function(state) { return false; },
5473             pixels_per_point: function(state) { return 4; }
5474         },
5475         "raphael": {
5476             initialize: NETDATA.raphaelInitialize,
5477             create: NETDATA.raphaelChartCreate,
5478             update: NETDATA.raphaelChartUpdate,
5479             resize: null,
5480             setSelection: undefined, // function(state, t) { return true; },
5481             clearSelection: undefined, // function(state) { return true; },
5482             toolboxPanAndZoom: null,
5483             initialized: false,
5484             enabled: true,
5485             format: function(state) { return 'json'; },
5486             options: function(state) { return ''; },
5487             legend: function(state) { return null; },
5488             autoresize: function(state) { return false; },
5489             max_updates_to_recreate: function(state) { return 5000; },
5490             track_colors: function(state) { return false; },
5491             pixels_per_point: function(state) { return 3; }
5492         },
5493         "c3": {
5494             initialize: NETDATA.c3Initialize,
5495             create: NETDATA.c3ChartCreate,
5496             update: NETDATA.c3ChartUpdate,
5497             resize: null,
5498             setSelection: undefined, // function(state, t) { return true; },
5499             clearSelection: undefined, // function(state) { return true; },
5500             toolboxPanAndZoom: null,
5501             initialized: false,
5502             enabled: true,
5503             format: function(state) { return 'csvjsonarray'; },
5504             options: function(state) { return 'milliseconds'; },
5505             legend: function(state) { return null; },
5506             autoresize: function(state) { return false; },
5507             max_updates_to_recreate: function(state) { return 5000; },
5508             track_colors: function(state) { return false; },
5509             pixels_per_point: function(state) { return 15; }
5510         },
5511         "d3": {
5512             initialize: NETDATA.d3Initialize,
5513             create: NETDATA.d3ChartCreate,
5514             update: NETDATA.d3ChartUpdate,
5515             resize: null,
5516             setSelection: undefined, // function(state, t) { return true; },
5517             clearSelection: undefined, // function(state) { return true; },
5518             toolboxPanAndZoom: null,
5519             initialized: false,
5520             enabled: true,
5521             format: function(state) { return 'json'; },
5522             options: function(state) { return ''; },
5523             legend: function(state) { return null; },
5524             autoresize: function(state) { return false; },
5525             max_updates_to_recreate: function(state) { return 5000; },
5526             track_colors: function(state) { return false; },
5527             pixels_per_point: function(state) { return 3; }
5528         },
5529         "easypiechart": {
5530             initialize: NETDATA.easypiechartInitialize,
5531             create: NETDATA.easypiechartChartCreate,
5532             update: NETDATA.easypiechartChartUpdate,
5533             resize: null,
5534             setSelection: NETDATA.easypiechartSetSelection,
5535             clearSelection: NETDATA.easypiechartClearSelection,
5536             toolboxPanAndZoom: null,
5537             initialized: false,
5538             enabled: true,
5539             format: function(state) { return 'array'; },
5540             options: function(state) { return 'absolute'; },
5541             legend: function(state) { return null; },
5542             autoresize: function(state) { return false; },
5543             max_updates_to_recreate: function(state) { return 5000; },
5544             track_colors: function(state) { return true; },
5545             pixels_per_point: function(state) { return 3; },
5546             aspect_ratio: 100
5547         },
5548         "gauge": {
5549             initialize: NETDATA.gaugeInitialize,
5550             create: NETDATA.gaugeChartCreate,
5551             update: NETDATA.gaugeChartUpdate,
5552             resize: null,
5553             setSelection: NETDATA.gaugeSetSelection,
5554             clearSelection: NETDATA.gaugeClearSelection,
5555             toolboxPanAndZoom: null,
5556             initialized: false,
5557             enabled: true,
5558             format: function(state) { return 'array'; },
5559             options: function(state) { return 'absolute'; },
5560             legend: function(state) { return null; },
5561             autoresize: function(state) { return false; },
5562             max_updates_to_recreate: function(state) { return 5000; },
5563             track_colors: function(state) { return true; },
5564             pixels_per_point: function(state) { return 3; },
5565             aspect_ratio: 70
5566         }
5567     };
5568
5569     NETDATA.registerChartLibrary = function(library, url) {
5570         if(NETDATA.options.debug.libraries === true)
5571             console.log("registering chart library: " + library);
5572
5573         NETDATA.chartLibraries[library].url = url;
5574         NETDATA.chartLibraries[library].initialized = true;
5575         NETDATA.chartLibraries[library].enabled = true;
5576     };
5577
5578     // ----------------------------------------------------------------------------------------------------------------
5579     // Load required JS libraries and CSS
5580
5581     NETDATA.requiredJs = [
5582         {
5583             url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5584             async: false,
5585             isAlreadyLoaded: function() {
5586                 // check if bootstrap is loaded
5587                 if(typeof $().emulateTransitionEnd == 'function')
5588                     return true;
5589                 else {
5590                     if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5591                         return true;
5592                     else
5593                         return false;
5594                 }
5595             }
5596         },
5597         {
5598             url: NETDATA.serverDefault + 'lib/jquery.nanoscroller-0.8.7.min.js',
5599             isAlreadyLoaded: function() { return false; }
5600         }
5601     ];
5602
5603     NETDATA.requiredCSS = [
5604         {
5605             url: NETDATA.themes.current.bootstrap_css,
5606             isAlreadyLoaded: function() {
5607                 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5608                     return true;
5609                 else
5610                     return false;
5611             }
5612         },
5613         {
5614             url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.6.3',
5615             isAlreadyLoaded: function() { return false; }
5616         },
5617         {
5618             url: NETDATA.themes.current.dashboard_css,
5619             isAlreadyLoaded: function() { return false; }
5620         }
5621     ];
5622
5623     NETDATA.loadedRequiredJs = 0;
5624     NETDATA.loadRequiredJs = function(index, callback) {
5625         if(index >= NETDATA.requiredJs.length) {
5626             if(typeof callback === 'function')
5627                 callback();
5628             return;
5629         }
5630
5631         if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5632             NETDATA.loadedRequiredJs++;
5633             NETDATA.loadRequiredJs(++index, callback);
5634             return;
5635         }
5636
5637         if(NETDATA.options.debug.main_loop === true)
5638             console.log('loading ' + NETDATA.requiredJs[index].url);
5639
5640         var async = true;
5641         if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5642             async = false;
5643
5644         $.ajax({
5645             url: NETDATA.requiredJs[index].url,
5646             cache: true,
5647             dataType: "script",
5648             xhrFields: { withCredentials: true } // required for the cookie
5649         })
5650         .done(function() {
5651             if(NETDATA.options.debug.main_loop === true)
5652                 console.log('loaded ' + NETDATA.requiredJs[index].url);
5653         })
5654         .fail(function() {
5655             alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5656         })
5657         .always(function() {
5658             NETDATA.loadedRequiredJs++;
5659
5660             if(async === false)
5661                 NETDATA.loadRequiredJs(++index, callback);
5662         })
5663
5664         if(async === true)
5665             NETDATA.loadRequiredJs(++index, callback);
5666     };
5667
5668     NETDATA.loadRequiredCSS = function(index) {
5669         if(index >= NETDATA.requiredCSS.length)
5670             return;
5671
5672         if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5673             NETDATA.loadRequiredCSS(++index);
5674             return;
5675         }
5676
5677         if(NETDATA.options.debug.main_loop === true)
5678             console.log('loading ' + NETDATA.requiredCSS[index].url);
5679
5680         NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5681         NETDATA.loadRequiredCSS(++index);
5682     };
5683
5684
5685     // ----------------------------------------------------------------------------------------------------------------
5686     // Registry of netdata hosts
5687
5688     NETDATA.alarms = {
5689         onclick: null,                  // the callback to handle the click - it will be called with the alarm log entry
5690         chart_div_offset: 100,          // give that space above the chart when scrolling to it
5691         chart_div_id_prefix: 'chart_',  // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
5692         chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
5693
5694         ms_penalty: 0,                  // the time penalty of the next alarm
5695         ms_between_notifications: 500,  // firefox moves the alarms off-screen (above, outside the top of the screen)
5696                                         // if alarms are shown faster than: one per 500ms
5697
5698         notifications: false,           // when true, the browser supports notifications (may not be granted though)
5699         last_notification_id: 0,        // the id of the last alarm_log we have raised an alarm for
5700         first_notification_id: 0,       // the id of the first alarm_log entry for this session
5701                                         // this is used to prevent CLEAR notifications for past events
5702         // notifications_shown: new Array(),
5703
5704         server: null,                   // the server to connect to for fetching alarms
5705         current: null,                  // the list of raised alarms - updated in the background
5706         callback: null,                 // a callback function to call every time the list of raised alarms is refreshed
5707
5708         notify: function(entry) {
5709             // console.log('alarm ' + entry.unique_id);
5710
5711             if(entry.updated === true) {
5712                 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
5713                 return;
5714             }
5715
5716             var value = entry.value;
5717             if(NETDATA.alarms.current !== null) {
5718                 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
5719                 if(typeof t !== 'undefined' && entry.status == t.status)
5720                     value = t.value;
5721             }
5722
5723             var name = entry.name.replace(/_/g, ' ');
5724             var status = entry.status.toLowerCase();
5725             var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
5726             var tag = entry.alarm_id;
5727             var icon = 'images/seo-performance-128.png';
5728             var interaction = false;
5729             var data = entry;
5730             var show = true;
5731
5732             // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' +  entry.status);
5733
5734             switch(entry.status) {
5735                 case 'REMOVED':
5736                     show = false;
5737                     break;
5738
5739                 case 'UNDEFINED':
5740                     return;
5741
5742                 case 'UNINITIALIZED':
5743                     return;
5744
5745                 case 'CLEAR':
5746                     if(entry.unique_id < NETDATA.alarms.first_notification_id) {
5747                         // console.log('alarm ' + entry.unique_id + ' is not current');
5748                         return;
5749                     }
5750                     if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
5751                         // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
5752                         return;
5753                     }
5754                     title = name + ' back to normal';
5755                     icon = 'images/check-mark-2-128-green.png'
5756                     interaction = false;
5757                     break;
5758
5759                 case 'WARNING':
5760                     if(entry.old_status === 'CRITICAL')
5761                         status = 'demoted to ' + entry.status.toLowerCase();
5762
5763                     icon = 'images/alert-128-orange.png';
5764                     interaction = false;
5765                     break;
5766
5767                 case 'CRITICAL':
5768                     if(entry.old_status === 'WARNING')
5769                         status = 'escalated to ' + entry.status.toLowerCase();
5770                     
5771                     icon = 'images/alert-128-red.png'
5772                     interaction = true;
5773                     break;
5774
5775                 default:
5776                     console.log('invalid alarm status ' + entry.status);
5777                     return;
5778             }
5779
5780             /*
5781             // cleanup old notifications with the same alarm_id as this one
5782             // FIXME: it does not seem to work on any web browser!
5783             var len = NETDATA.alarms.notifications_shown.length;
5784             while(len--) {
5785                 var n = NETDATA.alarms.notifications_shown[len];
5786                 if(n.data.alarm_id === entry.alarm_id) {
5787                     console.log('removing old alarm ' + n.data.unique_id);
5788
5789                     // close the notification
5790                     n.close.bind(n);
5791
5792                     // remove it from the array
5793                     NETDATA.alarms.notifications_shown.splice(len, 1);
5794                     len = NETDATA.alarms.notifications_shown.length;
5795                 }
5796             }
5797             */
5798
5799             if(show === true) {
5800
5801                 setTimeout(function() {
5802                     // show this notification
5803                     // console.log('new notification: ' + title);
5804                     var n = new Notification(title, {
5805                         body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
5806                         tag: tag,
5807                         requireInteraction: interaction,
5808                         icon: NETDATA.serverDefault + icon,
5809                         data: data
5810                     });
5811
5812                     n.onclick = function(event) {
5813                         event.preventDefault();
5814                         NETDATA.alarms.onclick(event.target.data);
5815                     };
5816
5817                     // console.log(n);
5818                     // NETDATA.alarms.notifications_shown.push(n);
5819                     // console.log(entry);
5820                 }, NETDATA.alarms.ms_penalty);
5821
5822                 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
5823             }
5824         },
5825
5826         scrollToChart: function(chart_id) {
5827             if(typeof chart_id === 'string') {
5828                 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
5829                 if(typeof offset !== 'undefined') {
5830                     $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
5831                     return true;
5832                 }
5833             }
5834             return false;
5835         },
5836
5837         scrollToAlarm: function(alarm) {
5838             if(typeof alarm === 'object') {
5839                 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
5840
5841                 if(ret === true && NETDATA.options.page_is_visible === false)
5842                     window.focus();
5843                 //    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.');
5844             }
5845
5846         },
5847
5848         notifyAll: function() {
5849             // console.log('FETCHING ALARM LOG');
5850             NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
5851                 // console.log('ALARM LOG FETCHED');
5852
5853                 if(data === null || typeof data !== 'object') {
5854                     console.log('invalid alarms log response');
5855                     return;
5856                 }
5857
5858                 if(data.length === 0) {
5859                     console.log('received empty alarm log');
5860                     return;
5861                 }
5862
5863                 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
5864
5865                 data.sort(function(a, b) {
5866                     if(a.unique_id > b.unique_id) return -1;
5867                     if(a.unique_id < b.unique_id) return 1;
5868                     return 0;
5869                 });
5870
5871                 NETDATA.alarms.ms_penalty = 0;
5872
5873                 var len = data.length;
5874                 while(len--) {
5875                     if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
5876                         NETDATA.alarms.notify(data[len]);
5877                     }
5878                     //else
5879                     //    console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
5880                 }
5881
5882                 NETDATA.alarms.last_notification_id = data[0].unique_id;
5883                 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
5884                 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
5885             })
5886         },
5887
5888         check_notifications: function() {
5889             // returns true if we should fire 1+ notifications
5890
5891             if(NETDATA.alarms.notifications !== true) {
5892                 // console.log('notifications not available');
5893                 return false;
5894             }
5895
5896             if(Notification.permission !== 'granted') {
5897                 // console.log('notifications not granted');
5898                 return false;
5899             }
5900
5901             if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
5902                 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
5903
5904                 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
5905                     // console.log('new alarms detected');
5906                     return true;
5907                 }
5908                 //else console.log('no new alarms');
5909             }
5910             // else console.log('cannot process alarms');
5911
5912             return false;
5913         },
5914
5915         get: function(what, callback) {
5916             $.ajax({
5917                 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
5918                 async: true,
5919                 cache: false,
5920                 xhrFields: { withCredentials: true } // required for the cookie
5921             })
5922                 .done(function(data) {
5923                     if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
5924                         NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
5925
5926                     if(typeof callback === 'function')
5927                         callback(data);
5928                 })
5929                 .fail(function() {
5930                     NETDATA.error(415, NETDATA.alarms.server);
5931
5932                     if(typeof callback === 'function')
5933                         callback(null);
5934                 });
5935         },
5936
5937         update_forever: function() {
5938             NETDATA.alarms.get('active', function(data) {
5939                 if(data !== null) {
5940                     NETDATA.alarms.current = data;
5941
5942                     if(NETDATA.alarms.check_notifications() === true) {
5943                         NETDATA.alarms.notifyAll();
5944                     }
5945
5946                     if (typeof NETDATA.alarms.callback === 'function') {
5947                         NETDATA.alarms.callback(data);
5948                     }
5949
5950                     // Health monitoring is disabled on this netdata
5951                     if(data.status === false) return;
5952                 }
5953
5954                 setTimeout(NETDATA.alarms.update_forever, 10000);
5955             });
5956         },
5957
5958         get_log: function(last_id, callback) {
5959             // console.log('fetching all log after ' + last_id.toString());
5960             $.ajax({
5961                 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
5962                 async: true,
5963                 cache: false,
5964                 xhrFields: { withCredentials: true } // required for the cookie
5965             })
5966                 .done(function(data) {
5967                     if(typeof callback === 'function')
5968                         callback(data);
5969                 })
5970                 .fail(function() {
5971                     NETDATA.error(416, NETDATA.alarms.server);
5972
5973                     if(typeof callback === 'function')
5974                         callback(null);
5975                 });
5976         },
5977
5978         init: function() {
5979             var host = NETDATA.serverDefault;
5980             while(host.slice(-1) === '/')
5981                 host = host.substring(0, host.length - 1);
5982             NETDATA.alarms.server = host;
5983
5984             NETDATA.alarms.last_notification_id = NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
5985
5986             if(NETDATA.alarms.onclick === null)
5987                 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
5988
5989             if(netdataShowAlarms === true) {
5990                 NETDATA.alarms.update_forever();
5991             
5992                 if('Notification' in window) {
5993                     // console.log('notifications available');
5994                     NETDATA.alarms.notifications = true;
5995
5996                     if(Notification.permission === 'default')
5997                         Notification.requestPermission();
5998                 }
5999             }
6000         }
6001     };
6002
6003     // ----------------------------------------------------------------------------------------------------------------
6004     // Registry of netdata hosts
6005
6006     NETDATA.registry = {
6007         server: null,       // the netdata registry server
6008         person_guid: null,  // the unique ID of this browser / user
6009         machine_guid: null, // the unique ID the netdata server that served dashboard.js
6010         hostname: null,     // the hostname of the netdata server that served dashboard.js
6011         machines: null,         // the user's other URLs
6012         machines_array: null,   // the user's other URLs in an array
6013         person_urls: null,
6014
6015         parsePersonUrls: function(person_urls) {
6016             // console.log(person_urls);
6017             NETDATA.registry.person_urls = person_urls;
6018
6019             if(person_urls) {
6020                 NETDATA.registry.machines = {};
6021                 NETDATA.registry.machines_array = new Array();
6022
6023                 var now = new Date().getTime();
6024                 var apu = person_urls;
6025                 var i = apu.length;
6026                 while(i--) {
6027                     if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6028                         // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6029
6030                         var obj = {
6031                             guid: apu[i][0],
6032                             url: apu[i][1],
6033                             last_t: apu[i][2],
6034                             accesses: apu[i][3],
6035                             name: apu[i][4],
6036                             alternate_urls: new Array()
6037                         };
6038                         obj.alternate_urls.push(apu[i][1]);
6039
6040                         NETDATA.registry.machines[apu[i][0]] = obj;
6041                         NETDATA.registry.machines_array.push(obj);
6042                     }
6043                     else {
6044                         // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6045
6046                         var pu = NETDATA.registry.machines[apu[i][0]];
6047                         if(pu.last_t < apu[i][2]) {
6048                             pu.url = apu[i][1];
6049                             pu.last_t = apu[i][2];
6050                             pu.name = apu[i][4];
6051                         }
6052                         pu.accesses += apu[i][3];
6053                         pu.alternate_urls.push(apu[i][1]);
6054                     }
6055                 }
6056             }
6057
6058             if(typeof netdataRegistryCallback === 'function')
6059                 netdataRegistryCallback(NETDATA.registry.machines_array);
6060         },
6061
6062         init: function() {
6063             if(netdataRegistry !== true) return;
6064
6065             NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6066                 if(data) {
6067                     NETDATA.registry.server = data.registry;
6068                     NETDATA.registry.machine_guid = data.machine_guid;
6069                     NETDATA.registry.hostname = data.hostname;
6070
6071                     NETDATA.registry.access(2, function (person_urls) {
6072                         NETDATA.registry.parsePersonUrls(person_urls);
6073
6074                     });
6075                 }
6076             });
6077         },
6078
6079         hello: function(host, callback) {
6080             while(host.slice(-1) === '/')
6081                 host = host.substring(0, host.length - 1);
6082
6083             // send HELLO to a netdata server:
6084             // 1. verifies the server is reachable
6085             // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6086             $.ajax({
6087                     url: host + '/api/v1/registry?action=hello',
6088                     async: true,
6089                     cache: false,
6090                     xhrFields: { withCredentials: true } // required for the cookie
6091                 })
6092                 .done(function(data) {
6093                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6094                         NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6095                         data = null;
6096                     }
6097
6098                     if(typeof callback === 'function')
6099                         callback(data);
6100                 })
6101                 .fail(function() {
6102                     NETDATA.error(407, host);
6103
6104                     if(typeof callback === 'function')
6105                         callback(null);
6106                 });
6107         },
6108
6109         access: function(max_redirects, callback) {
6110             // send ACCESS to a netdata registry:
6111             // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6112             // 2. it responds with a list of netdata servers we know
6113             // the registry identifies us using a cookie it sets the first time we access it
6114             // the registry may respond with a redirect URL to send us to another registry
6115             $.ajax({
6116                     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),
6117                     async: true,
6118                     cache: false,
6119                     xhrFields: { withCredentials: true } // required for the cookie
6120                 })
6121                 .done(function(data) {
6122                     var redirect = null;
6123                     if(typeof data.registry === 'string')
6124                         redirect = data.registry;
6125
6126                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6127                         NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6128                         data = null;
6129                     }
6130
6131                     if(data === null) {
6132                         if(redirect !== null && max_redirects > 0) {
6133                             NETDATA.registry.server = redirect;
6134                             NETDATA.registry.access(max_redirects - 1, callback);
6135                         }
6136                         else {
6137                             if(typeof callback === 'function')
6138                                 callback(null);
6139                         }
6140                     }
6141                     else {
6142                         if(typeof data.person_guid === 'string')
6143                             NETDATA.registry.person_guid = data.person_guid;
6144
6145                         if(typeof callback === 'function')
6146                             callback(data.urls);
6147                     }
6148                 })
6149                 .fail(function() {
6150                     NETDATA.error(410, NETDATA.registry.server);
6151
6152                     if(typeof callback === 'function')
6153                         callback(null);
6154                 });
6155         },
6156
6157         delete: function(delete_url, callback) {
6158             // send DELETE to a netdata registry:
6159             $.ajax({
6160                 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),
6161                 async: true,
6162                 cache: false,
6163                 xhrFields: { withCredentials: true } // required for the cookie
6164             })
6165                 .done(function(data) {
6166                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6167                         NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6168                         data = null;
6169                     }
6170
6171                     if(typeof callback === 'function')
6172                         callback(data);
6173                 })
6174                 .fail(function() {
6175                     NETDATA.error(412, NETDATA.registry.server);
6176
6177                     if(typeof callback === 'function')
6178                         callback(null);
6179                 });
6180         },
6181
6182         search: function(machine_guid, callback) {
6183             // SEARCH for the URLs of a machine:
6184             $.ajax({
6185                 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,
6186                 async: true,
6187                 cache: false,
6188                 xhrFields: { withCredentials: true } // required for the cookie
6189             })
6190                 .done(function(data) {
6191                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6192                         NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6193                         data = null;
6194                     }
6195
6196                     if(typeof callback === 'function')
6197                         callback(data);
6198                 })
6199                 .fail(function() {
6200                     NETDATA.error(418, NETDATA.registry.server);
6201
6202                     if(typeof callback === 'function')
6203                         callback(null);
6204                 });
6205         },
6206
6207         switch: function(new_person_guid, callback) {
6208             // impersonate
6209             $.ajax({
6210                 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,
6211                 async: true,
6212                 cache: false,
6213                 xhrFields: { withCredentials: true } // required for the cookie
6214             })
6215                 .done(function(data) {
6216                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6217                         NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6218                         data = null;
6219                     }
6220
6221                     if(typeof callback === 'function')
6222                         callback(data);
6223                 })
6224                 .fail(function() {
6225                     NETDATA.error(414, NETDATA.registry.server);
6226
6227                     if(typeof callback === 'function')
6228                         callback(null);
6229                 });
6230         }
6231     };
6232
6233     // ----------------------------------------------------------------------------------------------------------------
6234     // Boot it!
6235
6236     if(typeof netdataPrepCallback === 'function')
6237         netdataPrepCallback();
6238
6239     NETDATA.errorReset();
6240     NETDATA.loadRequiredCSS(0);
6241
6242     NETDATA._loadjQuery(function() {
6243         NETDATA.loadRequiredJs(0, function() {
6244             if(typeof $().emulateTransitionEnd !== 'function') {
6245                 // bootstrap is not available
6246                 NETDATA.options.current.show_help = false;
6247             }
6248
6249             if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6250                 if(NETDATA.options.debug.main_loop === true)
6251                     console.log('starting chart refresh thread');
6252
6253                 NETDATA.start();
6254             }
6255         });
6256     });
6257
6258     // window.NETDATA = NETDATA;
6259 // })(window, document);