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