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