]> arthur.barton.de Git - netdata.git/blob - web/dashboard.js
allow custom dashboards to link dimensions to DOM elements by name or id; fixes ...
[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: '#F0F0F0',
133             axis: '#F0F0F0',
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: Date.now(),       // 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 = Date.now();
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 = Date.now();
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 = Date.now();
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 = Date.now();
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 = Date.now();
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 = Date.now();
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 = Date.now();
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 = Date.now();
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 = Date.now();
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 = Date.now();
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 = Date.now();
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 = Date.now();
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 = Date.now();
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 = Date.now();
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 = Date.now();
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 = Date.now() + ms;
1843             else
1844                 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + 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 > Date.now())
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 = Date.now() + 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-' + name.toLowerCase() + '-at') || null;
2424                 if(user_id === null)
2425                     user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2426                 if(user_id !== null) {
2427                     user_element = document.getElementById(user_id) || null;
2428                     if (user_element === null)
2429                         state.log('Cannot find element with id: ' + user_id);
2430                 }
2431
2432                 state.element_legend_childs.series[name] = {
2433                     name: document.createElement('span'),
2434                     value: document.createElement('span'),
2435                     user: user_element,
2436                     last: null
2437                 };
2438
2439                 var label = state.element_legend_childs.series[name];
2440
2441                 // create the dimension visibility tracking for this label
2442                 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2443
2444                 var rgb = NETDATA.colorHex2Rgb(color);
2445                 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2446                     + state.chart.chart_type
2447                     + '" style="background-color: '
2448                     + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2449                     + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2450
2451                 var text = document.createTextNode(' ' + name);
2452                 label.name.appendChild(text);
2453
2454                 if(count > 0)
2455                     parent.appendChild(document.createElement('br'));
2456
2457                 parent.appendChild(label.name);
2458                 parent.appendChild(label.value);
2459             };
2460
2461             var content = document.createElement('div');
2462
2463             if(this.hasLegend()) {
2464                 this.element_legend_childs = {
2465                     content: content,
2466                     resize_handler: document.createElement('div'),
2467                     toolbox: document.createElement('div'),
2468                     toolbox_left: document.createElement('div'),
2469                     toolbox_right: document.createElement('div'),
2470                     toolbox_reset: document.createElement('div'),
2471                     toolbox_zoomin: document.createElement('div'),
2472                     toolbox_zoomout: document.createElement('div'),
2473                     toolbox_volume: document.createElement('div'),
2474                     title_date: document.createElement('span'),
2475                     title_time: document.createElement('span'),
2476                     title_units: document.createElement('span'),
2477                     nano: document.createElement('div'),
2478                     nano_options: {
2479                         paneClass: 'netdata-legend-series-pane',
2480                         sliderClass: 'netdata-legend-series-slider',
2481                         contentClass: 'netdata-legend-series-content',
2482                         enabledClass: '__enabled',
2483                         flashedClass: '__flashed',
2484                         activeClass: '__active',
2485                         tabIndex: -1,
2486                         alwaysVisible: true,
2487                         sliderMinHeight: 10
2488                     },
2489                     series: {}
2490                 };
2491
2492                 this.element_legend.innerHTML = '';
2493
2494                 if(this.library.toolboxPanAndZoom !== null) {
2495
2496                     function get_pan_and_zoom_step(event) {
2497                         if (event.ctrlKey)
2498                             return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2499
2500                         else if (event.shiftKey)
2501                             return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2502
2503                         else if (event.altKey)
2504                             return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2505
2506                         else
2507                             return NETDATA.options.current.pan_and_zoom_factor;
2508                     }
2509
2510                     this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2511                     this.element.appendChild(this.element_legend_childs.toolbox);
2512
2513                     this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2514                     this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2515                     this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2516                     this.element_legend_childs.toolbox_left.onclick = function(e) {
2517                         e.preventDefault();
2518
2519                         var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2520                         var before = that.view_before - step;
2521                         var after = that.view_after - step;
2522                         if(after >= that.netdata_first)
2523                             that.library.toolboxPanAndZoom(that, after, before);
2524                     };
2525                     if(NETDATA.options.current.show_help === true)
2526                         $(this.element_legend_childs.toolbox_left).popover({
2527                         container: "body",
2528                         animation: false,
2529                         html: true,
2530                         trigger: 'hover',
2531                         placement: 'bottom',
2532                         delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2533                         title: 'Pan Left',
2534                         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>'
2535                     });
2536
2537
2538                     this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2539                     this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2540                     this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2541                     this.element_legend_childs.toolbox_reset.onclick = function(e) {
2542                         e.preventDefault();
2543                         NETDATA.resetAllCharts(that);
2544                     };
2545                     if(NETDATA.options.current.show_help === true)
2546                         $(this.element_legend_childs.toolbox_reset).popover({
2547                         container: "body",
2548                         animation: false,
2549                         html: true,
2550                         trigger: 'hover',
2551                         placement: 'bottom',
2552                         delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2553                         title: 'Chart Reset',
2554                         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>'
2555                     });
2556                     
2557                     this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2558                     this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2559                     this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2560                     this.element_legend_childs.toolbox_right.onclick = function(e) {
2561                         e.preventDefault();
2562                         var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2563                         var before = that.view_before + step;
2564                         var after = that.view_after + step;
2565                         if(before <= that.netdata_last)
2566                             that.library.toolboxPanAndZoom(that, after, before);
2567                     };
2568                     if(NETDATA.options.current.show_help === true)
2569                         $(this.element_legend_childs.toolbox_right).popover({
2570                         container: "body",
2571                         animation: false,
2572                         html: true,
2573                         trigger: 'hover',
2574                         placement: 'bottom',
2575                         delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2576                         title: 'Pan Right',
2577                         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>'
2578                     });
2579
2580                     
2581                     this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2582                     this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2583                     this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2584                     this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2585                         e.preventDefault();
2586                         var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2587                         var before = that.view_before - dt;
2588                         var after = that.view_after + dt;
2589                         that.library.toolboxPanAndZoom(that, after, before);
2590                     };
2591                     if(NETDATA.options.current.show_help === true)
2592                         $(this.element_legend_childs.toolbox_zoomin).popover({
2593                         container: "body",
2594                         animation: false,
2595                         html: true,
2596                         trigger: 'hover',
2597                         placement: 'bottom',
2598                         delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2599                         title: 'Chart Zoom In',
2600                         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>'
2601                     });
2602                     
2603                     this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2604                     this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2605                     this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2606                     this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2607                         e.preventDefault();
2608                         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);
2609                         var before = that.view_before + dt;
2610                         var after = that.view_after - dt;
2611
2612                         that.library.toolboxPanAndZoom(that, after, before);
2613                     };
2614                     if(NETDATA.options.current.show_help === true)
2615                         $(this.element_legend_childs.toolbox_zoomout).popover({
2616                         container: "body",
2617                         animation: false,
2618                         html: true,
2619                         trigger: 'hover',
2620                         placement: 'bottom',
2621                         delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2622                         title: 'Chart Zoom Out',
2623                         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>'
2624                     });
2625                     
2626                     //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2627                     //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2628                     //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2629                     //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2630                     //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2631                         //e.preventDefault();
2632                         //alert('clicked toolbox_volume on ' + that.id);
2633                     //}
2634                 }
2635                 else {
2636                     this.element_legend_childs.toolbox = null;
2637                     this.element_legend_childs.toolbox_left = null;
2638                     this.element_legend_childs.toolbox_reset = null;
2639                     this.element_legend_childs.toolbox_right = null;
2640                     this.element_legend_childs.toolbox_zoomin = null;
2641                     this.element_legend_childs.toolbox_zoomout = null;
2642                     this.element_legend_childs.toolbox_volume = null;
2643                 }
2644                 
2645                 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2646                 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2647                 this.element.appendChild(this.element_legend_childs.resize_handler);
2648                 if(NETDATA.options.current.show_help === true)
2649                     $(this.element_legend_childs.resize_handler).popover({
2650                     container: "body",
2651                     animation: false,
2652                     html: true,
2653                     trigger: 'hover',
2654                     placement: 'bottom',
2655                     delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2656                     title: 'Chart Resize',
2657                     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>'
2658                 });
2659
2660                 // mousedown event
2661                 this.element_legend_childs.resize_handler.onmousedown =
2662                     function(e) {
2663                         that.resizeHandler(e);
2664                     };
2665
2666                 // touchstart event
2667                 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2668                     that.resizeHandler(e);
2669                 }, false);
2670
2671                 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2672                 this.element_legend.appendChild(this.element_legend_childs.title_date);
2673
2674                 this.element_legend.appendChild(document.createElement('br'));
2675
2676                 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2677                 this.element_legend.appendChild(this.element_legend_childs.title_time);
2678
2679                 this.element_legend.appendChild(document.createElement('br'));
2680
2681                 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2682                 this.element_legend.appendChild(this.element_legend_childs.title_units);
2683
2684                 this.element_legend.appendChild(document.createElement('br'));
2685
2686                 this.element_legend_childs.nano.className = 'netdata-legend-series';
2687                 this.element_legend.appendChild(this.element_legend_childs.nano);
2688
2689                 content.className = 'netdata-legend-series-content';
2690                 this.element_legend_childs.nano.appendChild(content);
2691
2692                 if(NETDATA.options.current.show_help === true)
2693                     $(content).popover({
2694                     container: "body",
2695                     animation: false,
2696                     html: true,
2697                     trigger: 'hover',
2698                     placement: 'bottom',
2699                     title: 'Chart Legend',
2700                     delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2701                     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>'
2702                 });
2703             }
2704             else {
2705                 this.element_legend_childs = {
2706                     content: content,
2707                     resize_handler: null,
2708                     toolbox: null,
2709                     toolbox_left: null,
2710                     toolbox_right: null,
2711                     toolbox_reset: null,
2712                     toolbox_zoomin: null,
2713                     toolbox_zoomout: null,
2714                     toolbox_volume: null,
2715                     title_date: null,
2716                     title_time: null,
2717                     title_units: null,
2718                     nano: null,
2719                     nano_options: null,
2720                     series: {}
2721                 };
2722             }
2723
2724             if(this.data) {
2725                 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2726                 if(this.debug === true)
2727                     this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2728
2729                 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2730                     genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2731                 }
2732             }
2733             else {
2734                 var tmp = new Array();
2735                 for(var dim in this.chart.dimensions) {
2736                     tmp.push(this.chart.dimensions[dim].name);
2737                     genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2738                 }
2739                 this.element_legend_childs.series.labels_key = tmp.toString();
2740                 if(this.debug === true)
2741                     this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2742             }
2743
2744             // create a hidden div to be used for hidding
2745             // the original legend of the chart library
2746             var el = document.createElement('div');
2747             if(this.element_legend !== null)
2748                 this.element_legend.appendChild(el);
2749             el.style.display = 'none';
2750
2751             this.element_legend_childs.hidden = document.createElement('div');
2752             el.appendChild(this.element_legend_childs.hidden);
2753
2754             if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2755                 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2756
2757             this.legendShowLatestValues();
2758         };
2759
2760         this.hasLegend = function() {
2761             if(typeof this.___hasLegendCache___ !== 'undefined')
2762                 return this.___hasLegendCache___;
2763
2764             var leg = false;
2765             if(this.library && this.library.legend(this) === 'right-side') {
2766                 var legend = $(this.element).data('legend') || 'yes';
2767                 if(legend === 'yes') leg = true;
2768             }
2769
2770             this.___hasLegendCache___ = leg;
2771             return leg;
2772         };
2773
2774         this.legendWidth = function() {
2775             return (this.hasLegend())?140:0;
2776         };
2777
2778         this.legendHeight = function() {
2779             return $(this.element).height();
2780         };
2781
2782         this.chartWidth = function() {
2783             return $(this.element).width() - this.legendWidth();
2784         };
2785
2786         this.chartHeight = function() {
2787             return $(this.element).height();
2788         };
2789
2790         this.chartPixelsPerPoint = function() {
2791             // force an options provided detail
2792             var px = this.pixels_per_point;
2793
2794             if(this.library && px < this.library.pixels_per_point(this))
2795                 px = this.library.pixels_per_point(this);
2796
2797             if(px < NETDATA.options.current.pixels_per_point)
2798                 px = NETDATA.options.current.pixels_per_point;
2799
2800             return px;
2801         };
2802
2803         this.needsRecreation = function() {
2804             return (
2805                     this.chart_created === true
2806                     && this.library
2807                     && this.library.autoresize() === false
2808                     && this.tm.last_resized < NETDATA.options.last_resized
2809                 );
2810         };
2811
2812         this.chartURL = function() {
2813             var after, before, points_multiplier = 1;
2814             if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2815                 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2816
2817                 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2818                 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2819                 this.view_after = after * 1000;
2820                 this.view_before = before * 1000;
2821
2822                 this.requested_padding = null;
2823                 points_multiplier = 1;
2824             }
2825             else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2826                 this.tm.pan_and_zoom_seq = 0;
2827
2828                 before = Math.round(this.current.force_before_ms / 1000);
2829                 after  = Math.round(this.current.force_after_ms / 1000);
2830                 this.view_after = after * 1000;
2831                 this.view_before = before * 1000;
2832
2833                 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2834                     this.requested_padding = Math.round((before - after) / 2);
2835                     after -= this.requested_padding;
2836                     before += this.requested_padding;
2837                     this.requested_padding *= 1000;
2838                     points_multiplier = 2;
2839                 }
2840
2841                 this.current.force_before_ms = null;
2842                 this.current.force_after_ms = null;
2843             }
2844             else {
2845                 this.tm.pan_and_zoom_seq = 0;
2846
2847                 before = this.before;
2848                 after  = this.after;
2849                 this.view_after = after * 1000;
2850                 this.view_before = before * 1000;
2851
2852                 this.requested_padding = null;
2853                 points_multiplier = 1;
2854             }
2855
2856             this.requested_after = after * 1000;
2857             this.requested_before = before * 1000;
2858
2859             this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2860
2861             // build the data URL
2862             this.data_url = this.host + this.chart.data_url;
2863             this.data_url += "&format="  + this.library.format();
2864             this.data_url += "&points="  + (this.data_points * points_multiplier).toString();
2865             this.data_url += "&group="   + this.method;
2866             this.data_url += "&options=" + this.library.options(this);
2867             this.data_url += '|jsonwrap';
2868
2869             if(NETDATA.options.current.eliminate_zero_dimensions === true)
2870                 this.data_url += '|nonzero';
2871
2872             if(this.append_options !== null)
2873                 this.data_url += '|' + this.append_options.toString();
2874
2875             if(after)
2876                 this.data_url += "&after="  + after.toString();
2877
2878             if(before)
2879                 this.data_url += "&before=" + before.toString();
2880
2881             if(this.dimensions)
2882                 this.data_url += "&dimensions=" + this.dimensions;
2883
2884             if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2885                 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2886         };
2887
2888         this.redrawChart = function() {
2889             if(this.data !== null)
2890                 this.updateChartWithData(this.data);
2891         };
2892
2893         this.updateChartWithData = function(data) {
2894             if(this.debug === true)
2895                 this.log('updateChartWithData() called.');
2896
2897             // this may force the chart to be re-created
2898             resizeChart();
2899
2900             this.data = data;
2901             this.updates_counter++;
2902             this.updates_since_last_unhide++;
2903             this.updates_since_last_creation++;
2904
2905             var started = Date.now();
2906
2907             // if the result is JSON, find the latest update-every
2908             this.data_update_every = data.view_update_every * 1000;
2909             this.data_after = data.after * 1000;
2910             this.data_before = data.before * 1000;
2911             this.netdata_first = data.first_entry * 1000;
2912             this.netdata_last = data.last_entry * 1000;
2913             this.data_points = data.points;
2914             data.state = this;
2915
2916             if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2917                 if(this.view_after < this.data_after) {
2918                     // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2919                     this.view_after = this.data_after;
2920                 }
2921
2922                 if(this.view_before > this.data_before) {
2923                     // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2924                     this.view_before = this.data_before;
2925                 }
2926             }
2927             else {
2928                 this.view_after = this.data_after;
2929                 this.view_before = this.data_before;
2930             }
2931
2932             if(this.debug === true) {
2933                 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2934
2935                 if(this.current.force_after_ms)
2936                     this.log('STATUS: forced    : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2937                 else
2938                     this.log('STATUS: forced    : unset');
2939
2940                 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2941                 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2942                 this.log('STATUS: rendered  : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2943                 this.log('STATUS: points    : ' + (this.data_points).toString());
2944             }
2945
2946             if(this.data_points === 0) {
2947                 noDataToShow();
2948                 return;
2949             }
2950
2951             if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2952                 if(this.debug === true)
2953                     this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2954
2955                 this.chart_created = false;
2956             }
2957
2958             // check and update the legend
2959             this.legendUpdateDOM();
2960
2961             if(this.chart_created === true
2962                 && typeof this.library.update === 'function') {
2963
2964                 if(this.debug === true)
2965                     this.log('updating chart...');
2966
2967                 if(callChartLibraryUpdateSafely(data) === false)
2968                     return;
2969             }
2970             else {
2971                 if(this.debug === true)
2972                     this.log('creating chart...');
2973
2974                 if(callChartLibraryCreateSafely(data) === false)
2975                     return;
2976             }
2977             hideMessage();
2978             this.legendShowLatestValues();
2979             if(this.selected === true)
2980                 NETDATA.globalSelectionSync.stop();
2981
2982             // update the performance counters
2983             var now = Date.now();
2984             this.tm.last_updated = now;
2985
2986             // don't update last_autorefreshed if this chart is
2987             // forced to be updated with global PanAndZoom
2988             if(NETDATA.globalPanAndZoom.isActive())
2989                 this.tm.last_autorefreshed = 0;
2990             else {
2991                 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
2992                     this.tm.last_autorefreshed = now - (now % this.data_update_every);
2993                 else
2994                     this.tm.last_autorefreshed = now;
2995             }
2996
2997             this.refresh_dt_ms = now - started;
2998             NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2999
3000             if(this.refresh_dt_element !== null)
3001                 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
3002         };
3003
3004         this.updateChart = function(callback) {
3005             if(this.debug === true)
3006                 this.log('updateChart() called.');
3007
3008             if(this._updating === true) {
3009                 if(this.debug === true)
3010                     this.log('I am already updating...');
3011
3012                 if(typeof callback === 'function') callback();
3013                 return false;
3014             }
3015
3016             // due to late initialization of charts and libraries
3017             // we need to check this too
3018             if(this.enabled === false) {
3019                 if(this.debug === true)
3020                     this.log('I am not enabled');
3021
3022                 if(typeof callback === 'function') callback();
3023                 return false;
3024             }
3025
3026             if(canBeRendered() === false) {
3027                 if(typeof callback === 'function') callback();
3028                 return false;
3029             }
3030
3031             if(this.chart === null) {
3032                 this.getChart(function() { that.updateChart(callback); });
3033                 return false;
3034             }
3035
3036             if(this.library.initialized === false) {
3037                 if(this.library.enabled === true) {
3038                     this.library.initialize(function() { that.updateChart(callback); });
3039                     return false;
3040                 }
3041                 else {
3042                     error('chart library "' + this.library_name + '" is not available.');
3043                     if(typeof callback === 'function') callback();
3044                     return false;
3045                 }
3046             }
3047
3048             this.clearSelection();
3049             this.chartURL();
3050
3051             if(this.debug === true)
3052                 this.log('updating from ' + this.data_url);
3053
3054             NETDATA.statistics.refreshes_total++;
3055             NETDATA.statistics.refreshes_active++;
3056
3057             if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3058                 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3059
3060             this._updating = true;
3061
3062             this.xhr = $.ajax( {
3063                 url: this.data_url,
3064                 cache: false,
3065                 async: true,
3066                 headers: {
3067                     'Cache-Control': 'no-cache, no-store',
3068                     'Pragma': 'no-cache'
3069                 },
3070                 xhrFields: { withCredentials: true } // required for the cookie
3071             })
3072             .done(function(data) {
3073                 that.xhr = undefined;
3074
3075                 if(that.debug === true)
3076                     that.log('data received. updating chart.');
3077
3078                 that.updateChartWithData(data);
3079             })
3080             .fail(function(msg) {
3081                 that.xhr = undefined;
3082
3083                 if(msg.statusText !== 'abort')
3084                     error('data download failed for url: ' + that.data_url);
3085             })
3086             .always(function() {
3087                 that.xhr = undefined;
3088
3089                 NETDATA.statistics.refreshes_active--;
3090                 that._updating = false;
3091                 if(typeof callback === 'function') callback();
3092             });
3093
3094             return true;
3095         };
3096
3097         this.isVisible = function(nocache) {
3098             if(typeof nocache === 'undefined')
3099                 nocache = false;
3100
3101             // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3102
3103             // caching - we do not evaluate the charts visibility
3104             // if the page has not been scrolled since the last check
3105             if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3106                 return this.___isVisible___;
3107
3108             this.tm.last_visible_check = Date.now();
3109
3110             var wh = window.innerHeight;
3111             var x = this.element.getBoundingClientRect();
3112             var ret = 0;
3113             var tolerance = 0;
3114
3115             if(x.width === 0 || x.height === 0) {
3116                 hideChart();
3117                 this.___isVisible___ = false;
3118                 return this.___isVisible___;
3119             }
3120
3121             if(x.top < 0 && -x.top > x.height) {
3122                 // the chart is entirely above
3123                 ret = -x.top - x.height;
3124             }
3125             else if(x.top > wh) {
3126                 // the chart is entirely below
3127                 ret = x.top - wh;
3128             }
3129
3130             if(ret > tolerance) {
3131                 // the chart is too far
3132
3133                 hideChart();
3134                 this.___isVisible___ = false;
3135                 return this.___isVisible___;
3136             }
3137             else {
3138                 // the chart is inside or very close
3139
3140                 unhideChart();
3141                 this.___isVisible___ = true;
3142                 return this.___isVisible___;
3143             }
3144         };
3145
3146         this.isAutoRefreshable = function() {
3147             return (this.current.autorefresh);
3148         };
3149
3150         this.canBeAutoRefreshed = function() {
3151             var now = Date.now();
3152
3153             if(this.running === true) {
3154                 if(this.debug === true)
3155                     this.log('I am already running');
3156
3157                 return false;
3158             }
3159
3160             if(this.enabled === false) {
3161                 if(this.debug === true)
3162                     this.log('I am not enabled');
3163
3164                 return false;
3165             }
3166
3167             if(this.library === null || this.library.enabled === false) {
3168                 error('charting library "' + this.library_name + '" is not available');
3169                 if(this.debug === true)
3170                     this.log('My chart library ' + this.library_name + ' is not available');
3171
3172                 return false;
3173             }
3174
3175             if(this.isVisible() === false) {
3176                 if(NETDATA.options.debug.visibility === true || this.debug === true)
3177                     this.log('I am not visible');
3178
3179                 return false;
3180             }
3181
3182             if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3183                 if(this.debug === true)
3184                     this.log('timed force update detected - allowing this update');
3185
3186                 this.current.force_update_at = 0;
3187                 return true;
3188             }
3189
3190             if(this.isAutoRefreshable() === true) {
3191                 // allow the first update, even if the page is not visible
3192                 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3193                     if(NETDATA.options.debug.focus === true || this.debug === true)
3194                         this.log('canBeAutoRefreshed(): page does not have focus');
3195
3196                     return false;
3197                 }
3198
3199                 if(this.needsRecreation() === true) {
3200                     if(this.debug === true)
3201                         this.log('canBeAutoRefreshed(): needs re-creation.');
3202
3203                     return true;
3204                 }
3205
3206                 // options valid only for autoRefresh()
3207                 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3208                     if(NETDATA.globalPanAndZoom.isActive()) {
3209                         if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3210                             if(this.debug === true)
3211                                 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3212
3213                             return true;
3214                         }
3215                         else {
3216                             if(this.debug === true)
3217                                 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3218
3219                             return false;
3220                         }
3221                     }
3222
3223                     if(this.selected === true) {
3224                         if(this.debug === true)
3225                             this.log('canBeAutoRefreshed(): I have a selection in place.');
3226
3227                         return false;
3228                     }
3229
3230                     if(this.paused === true) {
3231                         if(this.debug === true)
3232                             this.log('canBeAutoRefreshed(): I am paused.');
3233
3234                         return false;
3235                     }
3236
3237                     if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3238                         if(this.debug === true)
3239                             this.log('canBeAutoRefreshed(): It is time to update me.');
3240
3241                         return true;
3242                     }
3243                 }
3244             }
3245
3246             return false;
3247         };
3248
3249         this.autoRefresh = function(callback) {
3250             if(this.canBeAutoRefreshed() === true && this.running === false) {
3251                 var state = this;
3252
3253                 state.running = true;
3254                 state.updateChart(function() {
3255                     state.running = false;
3256
3257                     if(typeof callback !== 'undefined')
3258                         callback();
3259                 });
3260             }
3261             else {
3262                 if(typeof callback !== 'undefined')
3263                     callback();
3264             }
3265         };
3266
3267         this._defaultsFromDownloadedChart = function(chart) {
3268             this.chart = chart;
3269             this.chart_url = chart.url;
3270             this.data_update_every = chart.update_every * 1000;
3271             this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3272             this.tm.last_info_downloaded = Date.now();
3273
3274             if(this.title === null)
3275                 this.title = chart.title;
3276
3277             if(this.units === null)
3278                 this.units = chart.units;
3279         };
3280
3281         // fetch the chart description from the netdata server
3282         this.getChart = function(callback) {
3283             this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3284             if(this.chart) {
3285                 this._defaultsFromDownloadedChart(this.chart);
3286                 if(typeof callback === 'function') callback();
3287             }
3288             else {
3289                 this.chart_url = "/api/v1/chart?chart=" + this.id;
3290
3291                 if(this.debug === true)
3292                     this.log('downloading ' + this.chart_url);
3293
3294                 $.ajax( {
3295                     url:  this.host + this.chart_url,
3296                     cache: false,
3297                     async: true,
3298                     xhrFields: { withCredentials: true } // required for the cookie
3299                 })
3300                 .done(function(chart) {
3301                     chart.url = that.chart_url;
3302                     that._defaultsFromDownloadedChart(chart);
3303                     NETDATA.chartRegistry.add(that.host, that.id, chart);
3304                 })
3305                 .fail(function() {
3306                     NETDATA.error(404, that.chart_url);
3307                     error('chart not found on url "' + that.chart_url + '"');
3308                 })
3309                 .always(function() {
3310                     if(typeof callback === 'function') callback();
3311                 });
3312             }
3313         };
3314
3315         // ============================================================================================================
3316         // INITIALIZATION
3317
3318         init();
3319     };
3320
3321     NETDATA.resetAllCharts = function(state) {
3322         // first clear the global selection sync
3323         // to make sure no chart is in selected state
3324         state.globalSelectionSyncStop();
3325
3326         // there are 2 possibilities here
3327         // a. state is the global Pan and Zoom master
3328         // b. state is not the global Pan and Zoom master
3329         var master = true;
3330         if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3331             master = false;
3332
3333         // clear the global Pan and Zoom
3334         // this will also refresh the master
3335         // and unblock any charts currently mirroring the master
3336         NETDATA.globalPanAndZoom.clearMaster();
3337
3338         // if we were not the master, reset our status too
3339         // this is required because most probably the mouse
3340         // is over this chart, blocking it from auto-refreshing
3341         if(master === false && (state.paused === true || state.selected === true))
3342             state.resetChart();
3343     };
3344
3345     // get or create a chart state, given a DOM element
3346     NETDATA.chartState = function(element) {
3347         var state = $(element).data('netdata-state-object') || null;
3348         if(state === null) {
3349             state = new chartState(element);
3350             $(element).data('netdata-state-object', state);
3351         }
3352         return state;
3353     };
3354
3355     // ----------------------------------------------------------------------------------------------------------------
3356     // Library functions
3357
3358     // Load a script without jquery
3359     // This is used to load jquery - after it is loaded, we use jquery
3360     NETDATA._loadjQuery = function(callback) {
3361         if(typeof jQuery === 'undefined') {
3362             if(NETDATA.options.debug.main_loop === true)
3363                 console.log('loading ' + NETDATA.jQuery);
3364
3365             var script = document.createElement('script');
3366             script.type = 'text/javascript';
3367             script.async = true;
3368             script.src = NETDATA.jQuery;
3369
3370             // script.onabort = onError;
3371             script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3372             if(typeof callback === "function")
3373                 script.onload = callback;
3374
3375             var s = document.getElementsByTagName('script')[0];
3376             s.parentNode.insertBefore(script, s);
3377         }
3378         else if(typeof callback === "function")
3379             callback();
3380     };
3381
3382     NETDATA._loadCSS = function(filename) {
3383         // don't use jQuery here
3384         // styles are loaded before jQuery
3385         // to eliminate showing an unstyled page to the user
3386
3387         var fileref = document.createElement("link");
3388         fileref.setAttribute("rel", "stylesheet");
3389         fileref.setAttribute("type", "text/css");
3390         fileref.setAttribute("href", filename);
3391
3392         if (typeof fileref !== 'undefined')
3393             document.getElementsByTagName("head")[0].appendChild(fileref);
3394     };
3395
3396     NETDATA.colorHex2Rgb = function(hex) {
3397         // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3398         var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3399             hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3400             return r + r + g + g + b + b;
3401         });
3402
3403         var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3404         return result ? {
3405             r: parseInt(result[1], 16),
3406             g: parseInt(result[2], 16),
3407             b: parseInt(result[3], 16)
3408         } : null;
3409     };
3410
3411     NETDATA.colorLuminance = function(hex, lum) {
3412         // validate hex string
3413         hex = String(hex).replace(/[^0-9a-f]/gi, '');
3414         if (hex.length < 6)
3415             hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3416
3417         lum = lum || 0;
3418
3419         // convert to decimal and change luminosity
3420         var rgb = "#", c, i;
3421         for (i = 0; i < 3; i++) {
3422             c = parseInt(hex.substr(i*2,2), 16);
3423             c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3424             rgb += ("00"+c).substr(c.length);
3425         }
3426
3427         return rgb;
3428     };
3429
3430     NETDATA.guid = function() {
3431         function s4() {
3432             return Math.floor((1 + Math.random()) * 0x10000)
3433                     .toString(16)
3434                     .substring(1);
3435             }
3436
3437             return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3438     };
3439
3440     NETDATA.zeropad = function(x) {
3441         if(x > -10 && x < 10) return '0' + x.toString();
3442         else return x.toString();
3443     };
3444
3445     // user function to signal us the DOM has been
3446     // updated.
3447     NETDATA.updatedDom = function() {
3448         NETDATA.options.updated_dom = true;
3449     };
3450
3451     NETDATA.ready = function(callback) {
3452         NETDATA.options.pauseCallback = callback;
3453     };
3454
3455     NETDATA.pause = function(callback) {
3456         if(NETDATA.options.pause === true)
3457             callback();
3458         else
3459             NETDATA.options.pauseCallback = callback;
3460     };
3461
3462     NETDATA.unpause = function() {
3463         NETDATA.options.pauseCallback = null;
3464         NETDATA.options.updated_dom = true;
3465         NETDATA.options.pause = false;
3466     };
3467
3468     // ----------------------------------------------------------------------------------------------------------------
3469
3470     // this is purely sequencial charts refresher
3471     // it is meant to be autonomous
3472     NETDATA.chartRefresherNoParallel = function(index) {
3473         if(NETDATA.options.debug.mail_loop === true)
3474             console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3475
3476         if(NETDATA.options.updated_dom === true) {
3477             // the dom has been updated
3478             // get the dom parts again
3479             NETDATA.parseDom(NETDATA.chartRefresher);
3480             return;
3481         }
3482         if(index >= NETDATA.options.targets.length) {
3483             if(NETDATA.options.debug.main_loop === true)
3484                 console.log('waiting to restart main loop...');
3485
3486             NETDATA.options.auto_refresher_fast_weight = 0;
3487
3488             setTimeout(function() {
3489                 NETDATA.chartRefresher();
3490             }, NETDATA.options.current.idle_between_loops);
3491         }
3492         else {
3493             var state = NETDATA.options.targets[index];
3494
3495             if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3496                 if(NETDATA.options.debug.main_loop === true)
3497                     console.log('fast rendering...');
3498
3499                 state.autoRefresh(function() {
3500                     NETDATA.chartRefresherNoParallel(++index);
3501                 });
3502             }
3503             else {
3504                 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3505                 NETDATA.options.auto_refresher_fast_weight = 0;
3506
3507                 setTimeout(function() {
3508                     state.autoRefresh(function() {
3509                         NETDATA.chartRefresherNoParallel(++index);
3510                     });
3511                 }, NETDATA.options.current.idle_between_charts);
3512             }
3513         }
3514     };
3515
3516     // this is part of the parallel refresher
3517     // its cause is to refresh sequencially all the charts
3518     // that depend on chart library initialization
3519     // it will call the parallel refresher back
3520     // as soon as it sees a chart that its chart library
3521     // is initialized
3522     NETDATA.chartRefresher_uninitialized = function() {
3523         if(NETDATA.options.updated_dom === true) {
3524             // the dom has been updated
3525             // get the dom parts again
3526             NETDATA.parseDom(NETDATA.chartRefresher);
3527             return;
3528         }
3529
3530         if(NETDATA.options.sequencial.length === 0)
3531             NETDATA.chartRefresher();
3532         else {
3533             var state = NETDATA.options.sequencial.pop();
3534             if(state.library.initialized === true)
3535                 NETDATA.chartRefresher();
3536             else
3537                 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3538         }
3539     };
3540
3541     NETDATA.chartRefresherWaitTime = function() {
3542         return NETDATA.options.current.idle_parallel_loops;
3543     };
3544
3545     // the default refresher
3546     // it will create 2 sets of charts:
3547     // - the ones that can be refreshed in parallel
3548     // - the ones that depend on something else
3549     // the first set will be executed in parallel
3550     // the second will be given to NETDATA.chartRefresher_uninitialized()
3551     NETDATA.chartRefresher = function() {
3552         // console.log('auto-refresher...');
3553
3554         if(NETDATA.options.pause === true) {
3555             // console.log('auto-refresher is paused');
3556             setTimeout(NETDATA.chartRefresher,
3557                 NETDATA.chartRefresherWaitTime());
3558             return;
3559         }
3560
3561         if(typeof NETDATA.options.pauseCallback === 'function') {
3562             // console.log('auto-refresher is calling pauseCallback');
3563             NETDATA.options.pause = true;
3564             NETDATA.options.pauseCallback();
3565             NETDATA.chartRefresher();
3566             return;
3567         }
3568
3569         if(NETDATA.options.current.parallel_refresher === false) {
3570             // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3571             NETDATA.chartRefresherNoParallel(0);
3572             return;
3573         }
3574
3575         if(NETDATA.options.updated_dom === true) {
3576             // the dom has been updated
3577             // get the dom parts again
3578             // console.log('auto-refresher is calling parseDom()');
3579             NETDATA.parseDom(NETDATA.chartRefresher);
3580             return;
3581         }
3582
3583         var parallel = new Array();
3584         var targets = NETDATA.options.targets;
3585         var len = targets.length;
3586         var state;
3587         while(len--) {
3588             state = targets[len];
3589             if(state.isVisible() === false || state.running === true)
3590                 continue;
3591
3592             if(state.library.initialized === false) {
3593                 if(state.library.enabled === true) {
3594                     state.library.initialize(NETDATA.chartRefresher);
3595                     return;
3596                 }
3597                 else {
3598                     state.error('chart library "' + state.library_name + '" is not enabled.');
3599                 }
3600             }
3601
3602             parallel.unshift(state);
3603         }
3604
3605         if(parallel.length > 0) {
3606             // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3607             // this will execute the jobs in parallel
3608             $(parallel).each(function() {
3609                 this.autoRefresh();
3610             })
3611         }
3612         //else {
3613         //    console.log('auto-refresher nothing to do');
3614         //}
3615
3616         // run the next refresh iteration
3617         setTimeout(NETDATA.chartRefresher,
3618             NETDATA.chartRefresherWaitTime());
3619     };
3620
3621     NETDATA.parseDom = function(callback) {
3622         NETDATA.options.last_page_scroll = Date.now();
3623         NETDATA.options.updated_dom = false;
3624
3625         var targets = $('div[data-netdata]'); //.filter(':visible');
3626
3627         if(NETDATA.options.debug.main_loop === true)
3628             console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3629
3630         NETDATA.options.targets = new Array();
3631         var len = targets.length;
3632         while(len--) {
3633             // the initialization will take care of sizing
3634             // and the "loading..." message
3635             NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3636         }
3637
3638         if(typeof callback === 'function') callback();
3639     };
3640
3641     // this is the main function - where everything starts
3642     NETDATA.start = function() {
3643         // this should be called only once
3644
3645         NETDATA.options.page_is_visible = true;
3646
3647         $(window).blur(function() {
3648             if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3649                 NETDATA.options.page_is_visible = false;
3650                 if(NETDATA.options.debug.focus === true)
3651                     console.log('Lost Focus!');
3652             }
3653         });
3654
3655         $(window).focus(function() {
3656             if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3657                 NETDATA.options.page_is_visible = true;
3658                 if(NETDATA.options.debug.focus === true)
3659                     console.log('Focus restored!');
3660             }
3661         });
3662
3663         if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3664             if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3665                 NETDATA.options.page_is_visible = false;
3666                 if(NETDATA.options.debug.focus === true)
3667                     console.log('Document has no focus!');
3668             }
3669         }
3670
3671         // bootstrap tab switching
3672         $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3673
3674         // bootstrap modal switching
3675         $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3676         $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3677
3678         // bootstrap collapse switching
3679         $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3680         $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3681
3682         NETDATA.parseDom(NETDATA.chartRefresher);
3683
3684         // Alarms initialization
3685         setTimeout(NETDATA.alarms.init, 1000);
3686
3687         // Registry initialization
3688         setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3689
3690         if(typeof netdataCallback === 'function')
3691             netdataCallback();
3692     };
3693
3694     // ----------------------------------------------------------------------------------------------------------------
3695     // peity
3696
3697     NETDATA.peityInitialize = function(callback) {
3698         if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3699             $.ajax({
3700                 url: NETDATA.peity_js,
3701                 cache: true,
3702                 dataType: "script",
3703                 xhrFields: { withCredentials: true } // required for the cookie
3704             })
3705             .done(function() {
3706                 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3707             })
3708             .fail(function() {
3709                 NETDATA.chartLibraries.peity.enabled = false;
3710                 NETDATA.error(100, NETDATA.peity_js);
3711             })
3712             .always(function() {
3713                 if(typeof callback === "function")
3714                     callback();
3715             });
3716         }
3717         else {
3718             NETDATA.chartLibraries.peity.enabled = false;
3719             if(typeof callback === "function")
3720                 callback();
3721         }
3722     };
3723
3724     NETDATA.peityChartUpdate = function(state, data) {
3725         state.peity_instance.innerHTML = data.result;
3726
3727         if(state.peity_options.stroke !== state.chartColors()[0]) {
3728             state.peity_options.stroke = state.chartColors()[0];
3729             if(state.chart.chart_type === 'line')
3730                 state.peity_options.fill = NETDATA.themes.current.background;
3731             else
3732                 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3733         }
3734
3735         $(state.peity_instance).peity('line', state.peity_options);
3736         return true;
3737     };
3738
3739     NETDATA.peityChartCreate = function(state, data) {
3740         state.peity_instance = document.createElement('div');
3741         state.element_chart.appendChild(state.peity_instance);
3742
3743         var self = $(state.element);
3744         state.peity_options = {
3745             stroke: NETDATA.themes.current.foreground,
3746             strokeWidth: self.data('peity-strokewidth') || 1,
3747             width: state.chartWidth(),
3748             height: state.chartHeight(),
3749             fill: NETDATA.themes.current.foreground
3750         };
3751
3752         NETDATA.peityChartUpdate(state, data);
3753         return true;
3754     };
3755
3756     // ----------------------------------------------------------------------------------------------------------------
3757     // sparkline
3758
3759     NETDATA.sparklineInitialize = function(callback) {
3760         if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3761             $.ajax({
3762                 url: NETDATA.sparkline_js,
3763                 cache: true,
3764                 dataType: "script",
3765                 xhrFields: { withCredentials: true } // required for the cookie
3766             })
3767             .done(function() {
3768                 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3769             })
3770             .fail(function() {
3771                 NETDATA.chartLibraries.sparkline.enabled = false;
3772                 NETDATA.error(100, NETDATA.sparkline_js);
3773             })
3774             .always(function() {
3775                 if(typeof callback === "function")
3776                     callback();
3777             });
3778         }
3779         else {
3780             NETDATA.chartLibraries.sparkline.enabled = false;
3781             if(typeof callback === "function")
3782                 callback();
3783         }
3784     };
3785
3786     NETDATA.sparklineChartUpdate = function(state, data) {
3787         state.sparkline_options.width = state.chartWidth();
3788         state.sparkline_options.height = state.chartHeight();
3789
3790         $(state.element_chart).sparkline(data.result, state.sparkline_options);
3791         return true;
3792     };
3793
3794     NETDATA.sparklineChartCreate = function(state, data) {
3795         var self = $(state.element);
3796         var type = self.data('sparkline-type') || 'line';
3797         var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3798         var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3799         var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3800         var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3801         var composite = self.data('sparkline-composite') || undefined;
3802         var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3803         var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3804         var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3805         var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3806         var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3807         var spotColor = self.data('sparkline-spotcolor') || undefined;
3808         var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3809         var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3810         var spotRadius = self.data('sparkline-spotradius') || undefined;
3811         var valueSpots = self.data('sparkline-valuespots') || undefined;
3812         var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3813         var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3814         var lineWidth = self.data('sparkline-linewidth') || undefined;
3815         var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3816         var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3817         var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3818         var xvalues = self.data('sparkline-xvalues') || undefined;
3819         var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3820         var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3821         var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3822         var disableInteraction = self.data('sparkline-disableinteraction') || false;
3823         var disableTooltips = self.data('sparkline-disabletooltips') || false;
3824         var disableHighlight = self.data('sparkline-disablehighlight') || false;
3825         var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3826         var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3827         var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3828         var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3829         var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3830         var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3831         var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3832         var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3833         var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3834         var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3835         var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3836         var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3837         var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3838         var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3839         var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3840         var animatedZooms = self.data('sparkline-animatedzooms') || false;
3841
3842         if(spotColor === 'disable') spotColor='';
3843         if(minSpotColor === 'disable') minSpotColor='';
3844         if(maxSpotColor === 'disable') maxSpotColor='';
3845
3846         state.sparkline_options = {
3847             type: type,
3848             lineColor: lineColor,
3849             fillColor: fillColor,
3850             chartRangeMin: chartRangeMin,
3851             chartRangeMax: chartRangeMax,
3852             composite: composite,
3853             enableTagOptions: enableTagOptions,
3854             tagOptionPrefix: tagOptionPrefix,
3855             tagValuesAttribute: tagValuesAttribute,
3856             disableHiddenCheck: disableHiddenCheck,
3857             defaultPixelsPerValue: defaultPixelsPerValue,
3858             spotColor: spotColor,
3859             minSpotColor: minSpotColor,
3860             maxSpotColor: maxSpotColor,
3861             spotRadius: spotRadius,
3862             valueSpots: valueSpots,
3863             highlightSpotColor: highlightSpotColor,
3864             highlightLineColor: highlightLineColor,
3865             lineWidth: lineWidth,
3866             normalRangeMin: normalRangeMin,
3867             normalRangeMax: normalRangeMax,
3868             drawNormalOnTop: drawNormalOnTop,
3869             xvalues: xvalues,
3870             chartRangeClip: chartRangeClip,
3871             chartRangeMinX: chartRangeMinX,
3872             chartRangeMaxX: chartRangeMaxX,
3873             disableInteraction: disableInteraction,
3874             disableTooltips: disableTooltips,
3875             disableHighlight: disableHighlight,
3876             highlightLighten: highlightLighten,
3877             highlightColor: highlightColor,
3878             tooltipContainer: tooltipContainer,
3879             tooltipClassname: tooltipClassname,
3880             tooltipChartTitle: state.title,
3881             tooltipFormat: tooltipFormat,
3882             tooltipPrefix: tooltipPrefix,
3883             tooltipSuffix: tooltipSuffix,
3884             tooltipSkipNull: tooltipSkipNull,
3885             tooltipValueLookups: tooltipValueLookups,
3886             tooltipFormatFieldlist: tooltipFormatFieldlist,
3887             tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3888             numberFormatter: numberFormatter,
3889             numberDigitGroupSep: numberDigitGroupSep,
3890             numberDecimalMark: numberDecimalMark,
3891             numberDigitGroupCount: numberDigitGroupCount,
3892             animatedZooms: animatedZooms,
3893             width: state.chartWidth(),
3894             height: state.chartHeight()
3895         };
3896
3897         $(state.element_chart).sparkline(data.result, state.sparkline_options);
3898         return true;
3899     };
3900
3901     // ----------------------------------------------------------------------------------------------------------------
3902     // dygraph
3903
3904     NETDATA.dygraph = {
3905         smooth: false
3906     };
3907
3908     NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3909         if(after < state.netdata_first)
3910             after = state.netdata_first;
3911
3912         if(before > state.netdata_last)
3913             before = state.netdata_last;
3914
3915         state.setMode('zoom');
3916         state.globalSelectionSyncStop();
3917         state.globalSelectionSyncDelay();
3918         state.dygraph_user_action = true;
3919         state.dygraph_force_zoom = true;
3920         state.updateChartPanOrZoom(after, before);
3921         NETDATA.globalPanAndZoom.setMaster(state, after, before);
3922     };
3923
3924     NETDATA.dygraphSetSelection = function(state, t) {
3925         if(typeof state.dygraph_instance !== 'undefined') {
3926             var r = state.calculateRowForTime(t);
3927             if(r !== -1)
3928                 state.dygraph_instance.setSelection(r);
3929             else {
3930                 state.dygraph_instance.clearSelection();
3931                 state.legendShowUndefined();
3932             }
3933         }
3934
3935         return true;
3936     };
3937
3938     NETDATA.dygraphClearSelection = function(state, t) {
3939         if(typeof state.dygraph_instance !== 'undefined') {
3940             state.dygraph_instance.clearSelection();
3941         }
3942         return true;
3943     };
3944
3945     NETDATA.dygraphSmoothInitialize = function(callback) {
3946         $.ajax({
3947             url: NETDATA.dygraph_smooth_js,
3948             cache: true,
3949             dataType: "script",
3950             xhrFields: { withCredentials: true } // required for the cookie
3951         })
3952         .done(function() {
3953             NETDATA.dygraph.smooth = true;
3954             smoothPlotter.smoothing = 0.3;
3955         })
3956         .fail(function() {
3957             NETDATA.dygraph.smooth = false;
3958         })
3959         .always(function() {
3960             if(typeof callback === "function")
3961                 callback();
3962         });
3963     };
3964
3965     NETDATA.dygraphInitialize = function(callback) {
3966         if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3967             $.ajax({
3968                 url: NETDATA.dygraph_js,
3969                 cache: true,
3970                 dataType: "script",
3971                 xhrFields: { withCredentials: true } // required for the cookie
3972             })
3973             .done(function() {
3974                 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3975             })
3976             .fail(function() {
3977                 NETDATA.chartLibraries.dygraph.enabled = false;
3978                 NETDATA.error(100, NETDATA.dygraph_js);
3979             })
3980             .always(function() {
3981                 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3982                     NETDATA.dygraphSmoothInitialize(callback);
3983                 else if(typeof callback === "function")
3984                     callback();
3985             });
3986         }
3987         else {
3988             NETDATA.chartLibraries.dygraph.enabled = false;
3989             if(typeof callback === "function")
3990                 callback();
3991         }
3992     };
3993
3994     NETDATA.dygraphChartUpdate = function(state, data) {
3995         var dygraph = state.dygraph_instance;
3996
3997         if(typeof dygraph === 'undefined')
3998             return NETDATA.dygraphChartCreate(state, data);
3999
4000         // when the chart is not visible, and hidden
4001         // if there is a window resize, dygraph detects
4002         // its element size as 0x0.
4003         // this will make it re-appear properly
4004
4005         if(state.tm.last_unhidden > state.dygraph_last_rendered)
4006             dygraph.resize();
4007
4008         var options = {
4009                 file: data.result.data,
4010                 colors: state.chartColors(),
4011                 labels: data.result.labels,
4012                 labelsDivWidth: state.chartWidth() - 70,
4013                 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4014         };
4015
4016         if(state.dygraph_force_zoom === true) {
4017             if(NETDATA.options.debug.dygraph === true || state.debug === true)
4018                 state.log('dygraphChartUpdate() forced zoom update');
4019
4020             options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4021             options.isZoomedIgnoreProgrammaticZoom = true;
4022             state.dygraph_force_zoom = false;
4023         }
4024         else if(state.current.name !== 'auto') {
4025             if(NETDATA.options.debug.dygraph === true || state.debug === true)
4026                 state.log('dygraphChartUpdate() loose update');
4027         }
4028         else {
4029             if(NETDATA.options.debug.dygraph === true || state.debug === true)
4030                 state.log('dygraphChartUpdate() strict update');
4031
4032             options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4033             options.isZoomedIgnoreProgrammaticZoom = true;
4034         }
4035
4036         options.valueRange = state.dygraph_options.valueRange;
4037
4038         var oldMax = null, oldMin = null;
4039         if(state.__commonMin !== null) {
4040             state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4041             oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4042         }
4043         if(state.__commonMax !== null) {
4044             state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4045             oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4046         }
4047
4048         if(state.dygraph_smooth_eligible === true) {
4049             if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4050                 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4051                 NETDATA.dygraphChartCreate(state, data);
4052                 return;
4053             }
4054         }
4055
4056         dygraph.updateOptions(options);
4057
4058         var redraw = false;
4059         if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4060             state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4061             options.valueRange[0] = NETDATA.commonMin.get(state);
4062             redraw = true;
4063         }
4064         if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4065             state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4066             options.valueRange[1] = NETDATA.commonMax.get(state);
4067             redraw = true;
4068         }
4069
4070         if(redraw === true) {
4071             // state.log('forcing redraw to adapt to common- min/max');
4072             dygraph.updateOptions(options);
4073         }
4074
4075         state.dygraph_last_rendered = Date.now();
4076         return true;
4077     };
4078
4079     NETDATA.dygraphChartCreate = function(state, data) {
4080         if(NETDATA.options.debug.dygraph === true || state.debug === true)
4081             state.log('dygraphChartCreate()');
4082
4083         var self = $(state.element);
4084
4085         var chart_type = state.chart.chart_type;
4086         if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4087         chart_type = self.data('dygraph-type') || chart_type;
4088
4089         var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
4090         smooth = self.data('dygraph-smooth') || smooth;
4091
4092         if(NETDATA.dygraph.smooth === false)
4093             smooth = false;
4094
4095         var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
4096         var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
4097
4098         state.dygraph_options = {
4099             colors: self.data('dygraph-colors') || state.chartColors(),
4100
4101             // leave a few pixels empty on the right of the chart
4102             rightGap: self.data('dygraph-rightgap') || 5,
4103             showRangeSelector: self.data('dygraph-showrangeselector') || false,
4104             showRoller: self.data('dygraph-showroller') || false,
4105
4106             title: self.data('dygraph-title') || state.title,
4107             titleHeight: self.data('dygraph-titleheight') || 19,
4108
4109             legend: self.data('dygraph-legend') || 'always', // we need this to get selection events
4110             labels: data.result.labels,
4111             labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
4112             labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
4113             labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
4114             labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
4115             labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
4116             labelsKMB: false,
4117             labelsKMG2: false,
4118             showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
4119             hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
4120
4121             includeZero: self.data('dygraph-includezero') || ((chart_type === 'stacked')? true : false),
4122             xRangePad: self.data('dygraph-xrangepad') || 0,
4123             yRangePad: self.data('dygraph-yrangepad') || 1,
4124
4125             valueRange: self.data('dygraph-valuerange') || [ null, null ],
4126
4127             ylabel: state.units,
4128             yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
4129
4130             // the function to plot the chart
4131             plotter: null,
4132
4133             // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
4134             strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
4135             strokePattern: self.data('dygraph-strokepattern') || undefined,
4136
4137             // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
4138             // i.e. there is a missing point on either side of it. This also controls the size of those dots.
4139             drawPoints: self.data('dygraph-drawpoints') || false,
4140
4141             // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
4142             drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
4143
4144             connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
4145             pointSize: self.data('dygraph-pointsize') || 1,
4146
4147             // enabling this makes the chart with little square lines
4148             stepPlot: self.data('dygraph-stepplot') || false,
4149
4150             // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
4151             strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
4152             strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
4153
4154             fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
4155             fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
4156             stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
4157             stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
4158
4159             drawAxis: self.data('dygraph-drawaxis') || true,
4160             axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
4161             axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
4162             axisLineWidth: self.data('dygraph-axislinewidth') || 1.0,
4163
4164             drawGrid: self.data('dygraph-drawgrid') || true,
4165             gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4166             gridLineWidth: self.data('dygraph-gridlinewidth') || 1.0,
4167             gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4168
4169             maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4170             sigFigs: self.data('dygraph-sigfigs') || null,
4171             digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4172             valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4173
4174             highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4175             highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4176             highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4177
4178             pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4179             visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4180             axes: {
4181                 x: {
4182                     pixelsPerLabel: 50,
4183                     ticker: Dygraph.dateTicker,
4184                     axisLabelFormatter: function (d, gran) {
4185                         return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4186                     },
4187                     valueFormatter: function (ms) {
4188                         var d = new Date(ms);
4189                         return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4190                         // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4191                     }
4192                 },
4193                 y: {
4194                     pixelsPerLabel: 15,
4195                     valueFormatter: function (x) {
4196                         // we format legends with the state object
4197                         // no need to do anything here
4198                         // return (Math.round(x*100) / 100).toLocaleString();
4199                         // return state.legendFormatValue(x);
4200                         return x;
4201                     }
4202                 }
4203             },
4204             legendFormatter: function(data) {
4205                 var elements = state.element_legend_childs;
4206
4207                 // if the hidden div is not there
4208                 // we are not managing the legend
4209                 if(elements.hidden === null) return;
4210
4211                 if (typeof data.x !== 'undefined') {
4212                     state.legendSetDate(data.x);
4213                     var i = data.series.length;
4214                     while(i--) {
4215                         var series = data.series[i];
4216                         if(!series.isVisible) continue;
4217                         state.legendSetLabelValue(series.label, series.y);
4218                     }
4219                 }
4220
4221                 return '';
4222             },
4223             drawCallback: function(dygraph, is_initial) {
4224                 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4225                     state.dygraph_user_action = false;
4226
4227                     var x_range = dygraph.xAxisRange();
4228                     var after = Math.round(x_range[0]);
4229                     var before = Math.round(x_range[1]);
4230
4231                     if(NETDATA.options.debug.dygraph === true)
4232                         state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4233
4234                     if(before <= state.netdata_last && after >= state.netdata_first)
4235                         state.updateChartPanOrZoom(after, before);
4236                 }
4237             },
4238             zoomCallback: function(minDate, maxDate, yRanges) {
4239                 if(NETDATA.options.debug.dygraph === true)
4240                     state.log('dygraphZoomCallback()');
4241
4242                 state.globalSelectionSyncStop();
4243                 state.globalSelectionSyncDelay();
4244                 state.setMode('zoom');
4245
4246                 // refresh it to the greatest possible zoom level
4247                 state.dygraph_user_action = true;
4248                 state.dygraph_force_zoom = true;
4249                 state.updateChartPanOrZoom(minDate, maxDate);
4250             },
4251             highlightCallback: function(event, x, points, row, seriesName) {
4252                 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4253                     state.log('dygraphHighlightCallback()');
4254
4255                 state.pauseChart();
4256
4257                 // there is a bug in dygraph when the chart is zoomed enough
4258                 // the time it thinks is selected is wrong
4259                 // here we calculate the time t based on the row number selected
4260                 // which is ok
4261                 var t = state.data_after + row * state.data_update_every;
4262                 // 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);
4263
4264                 state.globalSelectionSync(x);
4265
4266                 // fix legend zIndex using the internal structures of dygraph legend module
4267                 // this works, but it is a hack!
4268                 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4269             },
4270             unhighlightCallback: function(event) {
4271                 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4272                     state.log('dygraphUnhighlightCallback()');
4273
4274                 state.unpauseChart();
4275                 state.globalSelectionSyncStop();
4276             },
4277             interactionModel : {
4278                 mousedown: function(event, dygraph, context) {
4279                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4280                         state.log('interactionModel.mousedown()');
4281
4282                     state.dygraph_user_action = true;
4283                     state.globalSelectionSyncStop();
4284
4285                     if(NETDATA.options.debug.dygraph === true)
4286                         state.log('dygraphMouseDown()');
4287
4288                     // Right-click should not initiate a zoom.
4289                     if(event.button && event.button === 2) return;
4290
4291                     context.initializeMouseDown(event, dygraph, context);
4292
4293                     if(event.button && event.button === 1) {
4294                         if (event.altKey || event.shiftKey) {
4295                             state.setMode('pan');
4296                             state.globalSelectionSyncDelay();
4297                             Dygraph.startPan(event, dygraph, context);
4298                         }
4299                         else {
4300                             state.setMode('zoom');
4301                             state.globalSelectionSyncDelay();
4302                             Dygraph.startZoom(event, dygraph, context);
4303                         }
4304                     }
4305                     else {
4306                         if (event.altKey || event.shiftKey) {
4307                             state.setMode('zoom');
4308                             state.globalSelectionSyncDelay();
4309                             Dygraph.startZoom(event, dygraph, context);
4310                         }
4311                         else {
4312                             state.setMode('pan');
4313                             state.globalSelectionSyncDelay();
4314                             Dygraph.startPan(event, dygraph, context);
4315                         }
4316                     }
4317                 },
4318                 mousemove: function(event, dygraph, context) {
4319                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4320                         state.log('interactionModel.mousemove()');
4321
4322                     if(context.isPanning) {
4323                         state.dygraph_user_action = true;
4324                         state.globalSelectionSyncStop();
4325                         state.globalSelectionSyncDelay();
4326                         state.setMode('pan');
4327                         context.is2DPan = false;
4328                         Dygraph.movePan(event, dygraph, context);
4329                     }
4330                     else if(context.isZooming) {
4331                         state.dygraph_user_action = true;
4332                         state.globalSelectionSyncStop();
4333                         state.globalSelectionSyncDelay();
4334                         state.setMode('zoom');
4335                         Dygraph.moveZoom(event, dygraph, context);
4336                     }
4337                 },
4338                 mouseup: function(event, dygraph, context) {
4339                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4340                         state.log('interactionModel.mouseup()');
4341
4342                     if (context.isPanning) {
4343                         state.dygraph_user_action = true;
4344                         state.globalSelectionSyncDelay();
4345                         Dygraph.endPan(event, dygraph, context);
4346                     }
4347                     else if (context.isZooming) {
4348                         state.dygraph_user_action = true;
4349                         state.globalSelectionSyncDelay();
4350                         Dygraph.endZoom(event, dygraph, context);
4351                     }
4352                 },
4353                 click: function(event, dygraph, context) {
4354                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4355                         state.log('interactionModel.click()');
4356
4357                     event.preventDefault();
4358                 },
4359                 dblclick: function(event, dygraph, context) {
4360                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4361                         state.log('interactionModel.dblclick()');
4362                     NETDATA.resetAllCharts(state);
4363                 },
4364                 wheel: function(event, dygraph, context) {
4365                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4366                         state.log('interactionModel.wheel()');
4367
4368                     // Take the offset of a mouse event on the dygraph canvas and
4369                     // convert it to a pair of percentages from the bottom left.
4370                     // (Not top left, bottom is where the lower value is.)
4371                     function offsetToPercentage(g, offsetX, offsetY) {
4372                         // This is calculating the pixel offset of the leftmost date.
4373                         var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4374                         var yar0 = g.yAxisRange(0);
4375
4376                         // This is calculating the pixel of the higest value. (Top pixel)
4377                         var yOffset = g.toDomCoords(null, yar0[1])[1];
4378
4379                         // x y w and h are relative to the corner of the drawing area,
4380                         // so that the upper corner of the drawing area is (0, 0).
4381                         var x = offsetX - xOffset;
4382                         var y = offsetY - yOffset;
4383
4384                         // This is computing the rightmost pixel, effectively defining the
4385                         // width.
4386                         var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4387
4388                         // This is computing the lowest pixel, effectively defining the height.
4389                         var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4390
4391                         // Percentage from the left.
4392                         var xPct = w === 0 ? 0 : (x / w);
4393                         // Percentage from the top.
4394                         var yPct = h === 0 ? 0 : (y / h);
4395
4396                         // The (1-) part below changes it from "% distance down from the top"
4397                         // to "% distance up from the bottom".
4398                         return [xPct, (1-yPct)];
4399                     }
4400
4401                     // Adjusts [x, y] toward each other by zoomInPercentage%
4402                     // Split it so the left/bottom axis gets xBias/yBias of that change and
4403                     // tight/top gets (1-xBias)/(1-yBias) of that change.
4404                     //
4405                     // If a bias is missing it splits it down the middle.
4406                     function zoomRange(g, zoomInPercentage, xBias, yBias) {
4407                         xBias = xBias || 0.5;
4408                         yBias = yBias || 0.5;
4409
4410                         function adjustAxis(axis, zoomInPercentage, bias) {
4411                             var delta = axis[1] - axis[0];
4412                             var increment = delta * zoomInPercentage;
4413                             var foo = [increment * bias, increment * (1-bias)];
4414
4415                             return [ axis[0] + foo[0], axis[1] - foo[1] ];
4416                         }
4417
4418                         var yAxes = g.yAxisRanges();
4419                         var newYAxes = [];
4420                         for (var i = 0; i < yAxes.length; i++) {
4421                             newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4422                         }
4423
4424                         return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4425                     }
4426
4427                     if(event.altKey || event.shiftKey) {
4428                         state.dygraph_user_action = true;
4429
4430                         state.globalSelectionSyncStop();
4431                         state.globalSelectionSyncDelay();
4432
4433                         // http://dygraphs.com/gallery/interaction-api.js
4434                         var normal_def;
4435                         if(typeof event.wheelDelta === 'number' && event.wheelDelta != NaN)
4436                             // chrome
4437                             normal_def = event.wheelDelta / 40;
4438                         else
4439                             // firefox
4440                             normal_def = event.deltaY * -1.2;
4441
4442                         var normal = (event.detail) ? event.detail * -1 : normal_def;
4443                         var percentage = normal / 50;
4444
4445                         if (!(event.offsetX && event.offsetY)){
4446                             event.offsetX = event.layerX - event.target.offsetLeft;
4447                             event.offsetY = event.layerY - event.target.offsetTop;
4448                         }
4449
4450                         var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4451                         var xPct = percentages[0];
4452                         var yPct = percentages[1];
4453
4454                         var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4455                         var after = new_x_range[0];
4456                         var before = new_x_range[1];
4457
4458                         var first = state.netdata_first + state.data_update_every;
4459                         var last = state.netdata_last + state.data_update_every;
4460
4461                         if(before > last) {
4462                             after -= (before - last);
4463                             before = last;
4464                         }
4465                         if(after < first) {
4466                             after = first;
4467                         }
4468
4469                         state.setMode('zoom');
4470                         if(state.updateChartPanOrZoom(after, before) === true)
4471                             dygraph.updateOptions({ dateWindow: [ after, before ] });
4472
4473                         event.preventDefault();
4474                     }
4475                 },
4476                 touchstart: function(event, dygraph, context) {
4477                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4478                         state.log('interactionModel.touchstart()');
4479
4480                     state.dygraph_user_action = true;
4481                     state.setMode('zoom');
4482                     state.pauseChart();
4483
4484                     Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4485
4486                     // we overwrite the touch directions at the end, to overwrite
4487                     // the internal default of dygraphs
4488                     context.touchDirections = { x: true, y: false };
4489
4490                     state.dygraph_last_touch_start = Date.now();
4491                     state.dygraph_last_touch_move = 0;
4492
4493                     if(typeof event.touches[0].pageX === 'number')
4494                         state.dygraph_last_touch_page_x = event.touches[0].pageX;
4495                     else
4496                         state.dygraph_last_touch_page_x = 0;
4497                 },
4498                 touchmove: function(event, dygraph, context) {
4499                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4500                         state.log('interactionModel.touchmove()');
4501
4502                     state.dygraph_user_action = true;
4503                     Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4504
4505                     state.dygraph_last_touch_move = Date.now();
4506                 },
4507                 touchend: function(event, dygraph, context) {
4508                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4509                         state.log('interactionModel.touchend()');
4510
4511                     state.dygraph_user_action = true;
4512                     Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4513
4514                     // if it didn't move, it is a selection
4515                     if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4516                         // internal api of dygraphs
4517                         var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4518                         var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4519                         if(NETDATA.dygraphSetSelection(state, t) === true)
4520                             state.globalSelectionSync(t);
4521                     }
4522
4523                     // if it was double tap within double click time, reset the charts
4524                     var now = Date.now();
4525                     if(typeof state.dygraph_last_touch_end !== 'undefined') {
4526                         if(state.dygraph_last_touch_move === 0) {
4527                             var dt = now - state.dygraph_last_touch_end;
4528                             if(dt <= NETDATA.options.current.double_click_speed)
4529                                 NETDATA.resetAllCharts(state);
4530                         }
4531                     }
4532
4533                     // remember the timestamp of the last touch end
4534                     state.dygraph_last_touch_end = now;
4535                 }
4536             }
4537         };
4538
4539         if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4540             state.dygraph_options.drawGrid = false;
4541             state.dygraph_options.drawAxis = false;
4542             state.dygraph_options.title = undefined;
4543             state.dygraph_options.ylabel = undefined;
4544             state.dygraph_options.yLabelWidth = 0;
4545             state.dygraph_options.labelsDivWidth = 120;
4546             state.dygraph_options.labelsDivStyles.width = '120px';
4547             state.dygraph_options.labelsSeparateLines = true;
4548             state.dygraph_options.rightGap = 0;
4549             state.dygraph_options.yRangePad = 1;
4550         }
4551
4552         if(smooth === true) {
4553             state.dygraph_smooth_eligible = true;
4554
4555             if(NETDATA.options.current.smooth_plot === true)
4556                 state.dygraph_options.plotter = smoothPlotter;
4557         }
4558         else state.dygraph_smooth_eligible = false;
4559
4560         state.dygraph_instance = new Dygraph(state.element_chart,
4561             data.result.data, state.dygraph_options);
4562
4563         state.dygraph_force_zoom = false;
4564         state.dygraph_user_action = false;
4565         state.dygraph_last_rendered = Date.now();
4566
4567         if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4568             state.__commonMin = self.data('common-min') || null;
4569             state.__commonMax = self.data('common-max') || null;
4570         }
4571         else {
4572             state.log('incompatible version of dygraphs detected');
4573             state.__commonMin = null;
4574             state.__commonMax = null;
4575         }
4576
4577         return true;
4578     };
4579
4580     // ----------------------------------------------------------------------------------------------------------------
4581     // morris
4582
4583     NETDATA.morrisInitialize = function(callback) {
4584         if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4585
4586             // morris requires raphael
4587             if(!NETDATA.chartLibraries.raphael.initialized) {
4588                 if(NETDATA.chartLibraries.raphael.enabled) {
4589                     NETDATA.raphaelInitialize(function() {
4590                         NETDATA.morrisInitialize(callback);
4591                     });
4592                 }
4593                 else {
4594                     NETDATA.chartLibraries.morris.enabled = false;
4595                     if(typeof callback === "function")
4596                         callback();
4597                 }
4598             }
4599             else {
4600                 NETDATA._loadCSS(NETDATA.morris_css);
4601
4602                 $.ajax({
4603                     url: NETDATA.morris_js,
4604                     cache: true,
4605                     dataType: "script",
4606                     xhrFields: { withCredentials: true } // required for the cookie
4607                 })
4608                 .done(function() {
4609                     NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4610                 })
4611                 .fail(function() {
4612                     NETDATA.chartLibraries.morris.enabled = false;
4613                     NETDATA.error(100, NETDATA.morris_js);
4614                 })
4615                 .always(function() {
4616                     if(typeof callback === "function")
4617                         callback();
4618                 });
4619             }
4620         }
4621         else {
4622             NETDATA.chartLibraries.morris.enabled = false;
4623             if(typeof callback === "function")
4624                 callback();
4625         }
4626     };
4627
4628     NETDATA.morrisChartUpdate = function(state, data) {
4629         state.morris_instance.setData(data.result.data);
4630         return true;
4631     };
4632
4633     NETDATA.morrisChartCreate = function(state, data) {
4634
4635         state.morris_options = {
4636                 element: state.element_chart.id,
4637                 data: data.result.data,
4638                 xkey: 'time',
4639                 ykeys: data.dimension_names,
4640                 labels: data.dimension_names,
4641                 lineWidth: 2,
4642                 pointSize: 3,
4643                 smooth: true,
4644                 hideHover: 'auto',
4645                 parseTime: true,
4646                 continuousLine: false,
4647                 behaveLikeLine: false
4648         };
4649
4650         if(state.chart.chart_type === 'line')
4651             state.morris_instance = new Morris.Line(state.morris_options);
4652
4653         else if(state.chart.chart_type === 'area') {
4654             state.morris_options.behaveLikeLine = true;
4655             state.morris_instance = new Morris.Area(state.morris_options);
4656         }
4657         else // stacked
4658             state.morris_instance = new Morris.Area(state.morris_options);
4659
4660         return true;
4661     };
4662
4663     // ----------------------------------------------------------------------------------------------------------------
4664     // raphael
4665
4666     NETDATA.raphaelInitialize = function(callback) {
4667         if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4668             $.ajax({
4669                 url: NETDATA.raphael_js,
4670                 cache: true,
4671                 dataType: "script",
4672                 xhrFields: { withCredentials: true } // required for the cookie
4673             })
4674             .done(function() {
4675                 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4676             })
4677             .fail(function() {
4678                 NETDATA.chartLibraries.raphael.enabled = false;
4679                 NETDATA.error(100, NETDATA.raphael_js);
4680             })
4681             .always(function() {
4682                 if(typeof callback === "function")
4683                     callback();
4684             });
4685         }
4686         else {
4687             NETDATA.chartLibraries.raphael.enabled = false;
4688             if(typeof callback === "function")
4689                 callback();
4690         }
4691     };
4692
4693     NETDATA.raphaelChartUpdate = function(state, data) {
4694         $(state.element_chart).raphael(data.result, {
4695             width: state.chartWidth(),
4696             height: state.chartHeight()
4697         });
4698
4699         return false;
4700     };
4701
4702     NETDATA.raphaelChartCreate = function(state, data) {
4703         $(state.element_chart).raphael(data.result, {
4704             width: state.chartWidth(),
4705             height: state.chartHeight()
4706         });
4707
4708         return false;
4709     };
4710
4711     // ----------------------------------------------------------------------------------------------------------------
4712     // C3
4713
4714     NETDATA.c3Initialize = function(callback) {
4715         if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4716
4717             // C3 requires D3
4718             if(!NETDATA.chartLibraries.d3.initialized) {
4719                 if(NETDATA.chartLibraries.d3.enabled) {
4720                     NETDATA.d3Initialize(function() {
4721                         NETDATA.c3Initialize(callback);
4722                     });
4723                 }
4724                 else {
4725                     NETDATA.chartLibraries.c3.enabled = false;
4726                     if(typeof callback === "function")
4727                         callback();
4728                 }
4729             }
4730             else {
4731                 NETDATA._loadCSS(NETDATA.c3_css);
4732
4733                 $.ajax({
4734                     url: NETDATA.c3_js,
4735                     cache: true,
4736                     dataType: "script",
4737                     xhrFields: { withCredentials: true } // required for the cookie
4738                 })
4739                 .done(function() {
4740                     NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4741                 })
4742                 .fail(function() {
4743                     NETDATA.chartLibraries.c3.enabled = false;
4744                     NETDATA.error(100, NETDATA.c3_js);
4745                 })
4746                 .always(function() {
4747                     if(typeof callback === "function")
4748                         callback();
4749                 });
4750             }
4751         }
4752         else {
4753             NETDATA.chartLibraries.c3.enabled = false;
4754             if(typeof callback === "function")
4755                 callback();
4756         }
4757     };
4758
4759     NETDATA.c3ChartUpdate = function(state, data) {
4760         state.c3_instance.destroy();
4761         return NETDATA.c3ChartCreate(state, data);
4762
4763         //state.c3_instance.load({
4764         //  rows: data.result,
4765         //  unload: true
4766         //});
4767
4768         //return true;
4769     };
4770
4771     NETDATA.c3ChartCreate = function(state, data) {
4772
4773         state.element_chart.id = 'c3-' + state.uuid;
4774         // console.log('id = ' + state.element_chart.id);
4775
4776         state.c3_instance = c3.generate({
4777             bindto: '#' + state.element_chart.id,
4778             size: {
4779                 width: state.chartWidth(),
4780                 height: state.chartHeight()
4781             },
4782             color: {
4783                 pattern: state.chartColors()
4784             },
4785             data: {
4786                 x: 'time',
4787                 rows: data.result,
4788                 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4789             },
4790             axis: {
4791                 x: {
4792                     type: 'timeseries',
4793                     tick: {
4794                         format: function(x) {
4795                             return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4796                         }
4797                     }
4798                 }
4799             },
4800             grid: {
4801                 x: {
4802                     show: true
4803                 },
4804                 y: {
4805                     show: true
4806                 }
4807             },
4808             point: {
4809                 show: false
4810             },
4811             line: {
4812                 connectNull: false
4813             },
4814             transition: {
4815                 duration: 0
4816             },
4817             interaction: {
4818                 enabled: true
4819             }
4820         });
4821
4822         // console.log(state.c3_instance);
4823
4824         return true;
4825     };
4826
4827     // ----------------------------------------------------------------------------------------------------------------
4828     // D3
4829
4830     NETDATA.d3Initialize = function(callback) {
4831         if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4832             $.ajax({
4833                 url: NETDATA.d3_js,
4834                 cache: true,
4835                 dataType: "script",
4836                 xhrFields: { withCredentials: true } // required for the cookie
4837             })
4838             .done(function() {
4839                 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4840             })
4841             .fail(function() {
4842                 NETDATA.chartLibraries.d3.enabled = false;
4843                 NETDATA.error(100, NETDATA.d3_js);
4844             })
4845             .always(function() {
4846                 if(typeof callback === "function")
4847                     callback();
4848             });
4849         }
4850         else {
4851             NETDATA.chartLibraries.d3.enabled = false;
4852             if(typeof callback === "function")
4853                 callback();
4854         }
4855     };
4856
4857     NETDATA.d3ChartUpdate = function(state, data) {
4858         return false;
4859     };
4860
4861     NETDATA.d3ChartCreate = function(state, data) {
4862         return false;
4863     };
4864
4865     // ----------------------------------------------------------------------------------------------------------------
4866     // google charts
4867
4868     NETDATA.googleInitialize = function(callback) {
4869         if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4870             $.ajax({
4871                 url: NETDATA.google_js,
4872                 cache: true,
4873                 dataType: "script",
4874                 xhrFields: { withCredentials: true } // required for the cookie
4875             })
4876             .done(function() {
4877                 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4878                 google.load('visualization', '1.1', {
4879                     'packages': ['corechart', 'controls'],
4880                     'callback': callback
4881                 });
4882             })
4883             .fail(function() {
4884                 NETDATA.chartLibraries.google.enabled = false;
4885                 NETDATA.error(100, NETDATA.google_js);
4886                 if(typeof callback === "function")
4887                     callback();
4888             });
4889         }
4890         else {
4891             NETDATA.chartLibraries.google.enabled = false;
4892             if(typeof callback === "function")
4893                 callback();
4894         }
4895     };
4896
4897     NETDATA.googleChartUpdate = function(state, data) {
4898         var datatable = new google.visualization.DataTable(data.result);
4899         state.google_instance.draw(datatable, state.google_options);
4900         return true;
4901     };
4902
4903     NETDATA.googleChartCreate = function(state, data) {
4904         var datatable = new google.visualization.DataTable(data.result);
4905
4906         state.google_options = {
4907             colors: state.chartColors(),
4908
4909             // do not set width, height - the chart resizes itself
4910             //width: state.chartWidth(),
4911             //height: state.chartHeight(),
4912             lineWidth: 1,
4913             title: state.title,
4914             fontSize: 11,
4915             hAxis: {
4916             //  title: "Time of Day",
4917             //  format:'HH:mm:ss',
4918                 viewWindowMode: 'maximized',
4919                 slantedText: false,
4920                 format:'HH:mm:ss',
4921                 textStyle: {
4922                     fontSize: 9
4923                 },
4924                 gridlines: {
4925                     color: '#EEE'
4926                 }
4927             },
4928             vAxis: {
4929                 title: state.units,
4930                 viewWindowMode: 'pretty',
4931                 minValue: -0.1,
4932                 maxValue: 0.1,
4933                 direction: 1,
4934                 textStyle: {
4935                     fontSize: 9
4936                 },
4937                 gridlines: {
4938                     color: '#EEE'
4939                 }
4940             },
4941             chartArea: {
4942                 width: '65%',
4943                 height: '80%'
4944             },
4945             focusTarget: 'category',
4946             annotation: {
4947                 '1': {
4948                     style: 'line'
4949                 }
4950             },
4951             pointsVisible: 0,
4952             titlePosition: 'out',
4953             titleTextStyle: {
4954                 fontSize: 11
4955             },
4956             tooltip: {
4957                 isHtml: false,
4958                 ignoreBounds: true,
4959                 textStyle: {
4960                     fontSize: 9
4961                 }
4962             },
4963             curveType: 'function',
4964             areaOpacity: 0.3,
4965             isStacked: false
4966         };
4967
4968         switch(state.chart.chart_type) {
4969             case "area":
4970                 state.google_options.vAxis.viewWindowMode = 'maximized';
4971                 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4972                 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4973                 break;
4974
4975             case "stacked":
4976                 state.google_options.isStacked = true;
4977                 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4978                 state.google_options.vAxis.viewWindowMode = 'maximized';
4979                 state.google_options.vAxis.minValue = null;
4980                 state.google_options.vAxis.maxValue = null;
4981                 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4982                 break;
4983
4984             default:
4985             case "line":
4986                 state.google_options.lineWidth = 2;
4987                 state.google_instance = new google.visualization.LineChart(state.element_chart);
4988                 break;
4989         }
4990
4991         state.google_instance.draw(datatable, state.google_options);
4992         return true;
4993     };
4994
4995     // ----------------------------------------------------------------------------------------------------------------
4996
4997     NETDATA.percentFromValueMax = function(value, max) {
4998         if(value === null) value = 0;
4999         if(max < value) max = value;
5000
5001         var pcent = 0;
5002         if(max !== 0) {
5003             pcent = Math.round(value * 100 / max);
5004             if(pcent === 0 && value > 0) pcent = 1;
5005         }
5006
5007         return pcent;
5008     };
5009
5010     // ----------------------------------------------------------------------------------------------------------------
5011     // easy-pie-chart
5012
5013     NETDATA.easypiechartInitialize = function(callback) {
5014         if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5015             $.ajax({
5016                 url: NETDATA.easypiechart_js,
5017                 cache: true,
5018                 dataType: "script",
5019                 xhrFields: { withCredentials: true } // required for the cookie
5020             })
5021                 .done(function() {
5022                     NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5023                 })
5024                 .fail(function() {
5025                     NETDATA.chartLibraries.easypiechart.enabled = false;
5026                     NETDATA.error(100, NETDATA.easypiechart_js);
5027                 })
5028                 .always(function() {
5029                     if(typeof callback === "function")
5030                         callback();
5031                 })
5032         }
5033         else {
5034             NETDATA.chartLibraries.easypiechart.enabled = false;
5035             if(typeof callback === "function")
5036                 callback();
5037         }
5038     };
5039
5040     NETDATA.easypiechartClearSelection = function(state) {
5041         if(typeof state.easyPieChartEvent !== 'undefined') {
5042             if(state.easyPieChartEvent.timer !== null)
5043                 clearTimeout(state.easyPieChartEvent.timer);
5044
5045             state.easyPieChartEvent.timer = null;
5046         }
5047
5048         if(state.isAutoRefreshable() === true && state.data !== null) {
5049             NETDATA.easypiechartChartUpdate(state, state.data);
5050         }
5051         else {
5052             state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
5053             state.easyPieChart_instance.update(0);
5054         }
5055         state.easyPieChart_instance.enableAnimation();
5056
5057         return true;
5058     };
5059
5060     NETDATA.easypiechartSetSelection = function(state, t) {
5061         if(state.timeIsVisible(t) !== true)
5062             return NETDATA.easypiechartClearSelection(state);
5063
5064         var slot = state.calculateRowForTime(t);
5065         if(slot < 0 || slot >= state.data.result.length)
5066             return NETDATA.easypiechartClearSelection(state);
5067
5068         if(typeof state.easyPieChartEvent === 'undefined') {
5069             state.easyPieChartEvent = {
5070                 timer: null,
5071                 value: 0,
5072                 pcent: 0
5073             };
5074         }
5075
5076         var value = state.data.result[state.data.result.length - 1 - slot];
5077         var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5078         var pcent = NETDATA.percentFromValueMax(value, max);
5079
5080         state.easyPieChartEvent.value = value;
5081         state.easyPieChartEvent.pcent = pcent;
5082         state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
5083
5084         if(state.easyPieChartEvent.timer === null) {
5085             state.easyPieChart_instance.disableAnimation();
5086
5087             state.easyPieChartEvent.timer = setTimeout(function() {
5088                 state.easyPieChartEvent.timer = null;
5089                 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5090             }, NETDATA.options.current.charts_selection_animation_delay);
5091         }
5092
5093         return true;
5094     };
5095
5096     NETDATA.easypiechartChartUpdate = function(state, data) {
5097         var value, max, pcent;
5098
5099         if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5100             value = null;
5101             max = 0;
5102             pcent = 0;
5103         }
5104         else {
5105             value = data.result[0];
5106             max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5107             pcent = NETDATA.percentFromValueMax(value, max);
5108         }
5109
5110         state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
5111         state.easyPieChart_instance.update(pcent);
5112         return true;
5113     };
5114
5115     NETDATA.easypiechartChartCreate = function(state, data) {
5116         var self = $(state.element);
5117         var chart = $(state.element_chart);
5118
5119         var value = data.result[0];
5120         var max = self.data('easypiechart-max-value') || null;
5121         var adjust = self.data('easypiechart-adjust') || null;
5122
5123         if(max === null) {
5124             max = NETDATA.commonMax.get(state);
5125             state.easyPieChartMax = null;
5126         }
5127         else
5128             state.easyPieChartMax = max;
5129
5130         var pcent = NETDATA.percentFromValueMax(value, max);
5131
5132         chart.data('data-percent', pcent);
5133
5134         var size;
5135         switch(adjust) {
5136             case 'width': size = state.chartHeight(); break;
5137             case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5138             case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5139             case 'height':
5140             default: size = state.chartWidth(); break;
5141         }
5142         state.element.style.width = size + 'px';
5143         state.element.style.height = size + 'px';
5144
5145         var stroke = Math.floor(size / 22);
5146         if(stroke < 3) stroke = 2;
5147
5148         var valuefontsize = Math.floor((size * 2 / 3) / 5);
5149         var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5150         state.easyPieChartLabel = document.createElement('span');
5151         state.easyPieChartLabel.className = 'easyPieChartLabel';
5152         state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
5153         state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5154         state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5155         state.element_chart.appendChild(state.easyPieChartLabel);
5156
5157         var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5158         var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5159         state.easyPieChartTitle = document.createElement('span');
5160         state.easyPieChartTitle.className = 'easyPieChartTitle';
5161         state.easyPieChartTitle.innerHTML = state.title;
5162         state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5163         state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5164         state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5165         state.element_chart.appendChild(state.easyPieChartTitle);
5166
5167         var unitfontsize = Math.round(titlefontsize * 0.9);
5168         var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5169         state.easyPieChartUnits = document.createElement('span');
5170         state.easyPieChartUnits.className = 'easyPieChartUnits';
5171         state.easyPieChartUnits.innerHTML = state.units;
5172         state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5173         state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5174         state.element_chart.appendChild(state.easyPieChartUnits);
5175
5176         var barColor = self.data('easypiechart-barcolor');
5177         if(typeof barColor === 'undefined' || barColor === null)
5178             barColor = state.chartColors()[0];
5179         else {
5180             // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5181             var tmp = eval(barColor);
5182             if(typeof tmp === 'function')
5183                 barColor = tmp;
5184         }
5185
5186         chart.easyPieChart({
5187             barColor: barColor,
5188             trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5189             scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5190             scaleLength: self.data('easypiechart-scalelength') || 5,
5191             lineCap: self.data('easypiechart-linecap') || 'round',
5192             lineWidth: self.data('easypiechart-linewidth') || stroke,
5193             trackWidth: self.data('easypiechart-trackwidth') || undefined,
5194             size: self.data('easypiechart-size') || size,
5195             rotate: self.data('easypiechart-rotate') || 0,
5196             animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
5197             easing: self.data('easypiechart-easing') || undefined
5198         });
5199
5200         // when we just re-create the chart
5201         // do not animate the first update
5202         var animate = true;
5203         if(typeof state.easyPieChart_instance !== 'undefined')
5204             animate = false;
5205
5206         state.easyPieChart_instance = chart.data('easyPieChart');
5207         if(animate === false) state.easyPieChart_instance.disableAnimation();
5208         state.easyPieChart_instance.update(pcent);
5209         if(animate === false) state.easyPieChart_instance.enableAnimation();
5210         return true;
5211     };
5212
5213     // ----------------------------------------------------------------------------------------------------------------
5214     // gauge.js
5215
5216     NETDATA.gaugeInitialize = function(callback) {
5217         if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5218             $.ajax({
5219                 url: NETDATA.gauge_js,
5220                 cache: true,
5221                 dataType: "script",
5222                 xhrFields: { withCredentials: true } // required for the cookie
5223             })
5224                 .done(function() {
5225                     NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5226                 })
5227                 .fail(function() {
5228                     NETDATA.chartLibraries.gauge.enabled = false;
5229                     NETDATA.error(100, NETDATA.gauge_js);
5230                 })
5231                 .always(function() {
5232                     if(typeof callback === "function")
5233                         callback();
5234                 })
5235         }
5236         else {
5237             NETDATA.chartLibraries.gauge.enabled = false;
5238             if(typeof callback === "function")
5239                 callback();
5240         }
5241     };
5242
5243     NETDATA.gaugeAnimation = function(state, status) {
5244         var speed = 32;
5245
5246         if(typeof status === 'boolean' && status === false)
5247             speed = 1000000000;
5248         else if(typeof status === 'number')
5249             speed = status;
5250
5251         // console.log('gauge speed ' + speed);
5252         state.gauge_instance.animationSpeed = speed;
5253         state.___gaugeOld__.speed = speed;
5254     };
5255
5256     NETDATA.gaugeSet = function(state, value, min, max) {
5257         if(typeof value !== 'number') value = 0;
5258         if(typeof min !== 'number') min = 0;
5259         if(typeof max !== 'number') max = 0;
5260         if(value > max) max = value;
5261         if(value < min) min = value;
5262         if(min > max) {
5263             var t = min;
5264             min = max;
5265             max = t;
5266         }
5267         else if(min == max)
5268             max = min + 1;
5269
5270         // gauge.js has an issue if the needle
5271         // is smaller than min or larger than max
5272         // when we set the new values
5273         // the needle will go crazy
5274
5275         // to prevent it, we always feed it
5276         // with a percentage, so that the needle
5277         // is always between min and max
5278         var pcent = (value - min) * 100 / (max - min);
5279
5280         // these should never happen
5281         if(pcent < 0) pcent = 0;
5282         if(pcent > 100) pcent = 100;
5283
5284         state.gauge_instance.set(pcent);
5285         // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5286
5287         state.___gaugeOld__.value = value;
5288         state.___gaugeOld__.min = min;
5289         state.___gaugeOld__.max = max;
5290     };
5291
5292     NETDATA.gaugeSetLabels = function(state, value, min, max) {
5293         if(state.___gaugeOld__.valueLabel !== value) {
5294             state.___gaugeOld__.valueLabel = value;
5295             state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
5296         }
5297         if(state.___gaugeOld__.minLabel !== min) {
5298             state.___gaugeOld__.minLabel = min;
5299             state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
5300         }
5301         if(state.___gaugeOld__.maxLabel !== max) {
5302             state.___gaugeOld__.maxLabel = max;
5303             state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
5304         }
5305     };
5306
5307     NETDATA.gaugeClearSelection = function(state) {
5308         if(typeof state.gaugeEvent !== 'undefined') {
5309             if(state.gaugeEvent.timer !== null)
5310                 clearTimeout(state.gaugeEvent.timer);
5311
5312             state.gaugeEvent.timer = null;
5313         }
5314
5315         if(state.isAutoRefreshable() === true && state.data !== null) {
5316             NETDATA.gaugeChartUpdate(state, state.data);
5317         }
5318         else {
5319             NETDATA.gaugeAnimation(state, false);
5320             NETDATA.gaugeSet(state, null, null, null);
5321             NETDATA.gaugeSetLabels(state, null, null, null);
5322         }
5323
5324         NETDATA.gaugeAnimation(state, true);
5325         return true;
5326     };
5327
5328     NETDATA.gaugeSetSelection = function(state, t) {
5329         if(state.timeIsVisible(t) !== true)
5330             return NETDATA.gaugeClearSelection(state);
5331
5332         var slot = state.calculateRowForTime(t);
5333         if(slot < 0 || slot >= state.data.result.length)
5334             return NETDATA.gaugeClearSelection(state);
5335
5336         if(typeof state.gaugeEvent === 'undefined') {
5337             state.gaugeEvent = {
5338                 timer: null,
5339                 value: 0,
5340                 min: 0,
5341                 max: 0
5342             };
5343         }
5344
5345         var value = state.data.result[state.data.result.length - 1 - slot];
5346         var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5347         var min = 0;
5348
5349         state.gaugeEvent.value = value;
5350         state.gaugeEvent.max = max;
5351         state.gaugeEvent.min = min;
5352         NETDATA.gaugeSetLabels(state, value, min, max);
5353
5354         if(state.gaugeEvent.timer === null) {
5355             NETDATA.gaugeAnimation(state, false);
5356
5357             state.gaugeEvent.timer = setTimeout(function() {
5358                 state.gaugeEvent.timer = null;
5359                 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5360             }, NETDATA.options.current.charts_selection_animation_delay);
5361         }
5362
5363         return true;
5364     };
5365
5366     NETDATA.gaugeChartUpdate = function(state, data) {
5367         var value, min, max;
5368
5369         if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5370             value = 0;
5371             min = 0;
5372             max = 1;
5373             NETDATA.gaugeSetLabels(state, null, null, null);
5374         }
5375         else {
5376             value = data.result[0];
5377             min = 0;
5378             max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5379             if(value > max) max = value;
5380             NETDATA.gaugeSetLabels(state, value, min, max);
5381         }
5382
5383         NETDATA.gaugeSet(state, value, min, max);
5384         return true;
5385     };
5386
5387     NETDATA.gaugeChartCreate = function(state, data) {
5388         var self = $(state.element);
5389         // var chart = $(state.element_chart);
5390
5391         var value = data.result[0];
5392         var max = self.data('gauge-max-value') || null;
5393         var adjust = self.data('gauge-adjust') || null;
5394         var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5395         var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5396         var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5397         var stopColor = self.data('gauge-stop-color') || void 0;
5398         var generateGradient = self.data('gauge-generate-gradient') || false;
5399
5400         if(max === null) {
5401             max = NETDATA.commonMax.get(state);
5402             state.gaugeMax = null;
5403         }
5404         else
5405             state.gaugeMax = max;
5406
5407         var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5408         //switch(adjust) {
5409         //  case 'width': width = height * ratio; break;
5410         //  case 'height':
5411         //  default: height = width / ratio; break;
5412         //}
5413         //state.element.style.width = width.toString() + 'px';
5414         //state.element.style.height = height.toString() + 'px';
5415
5416         var lum_d = 0.05;
5417
5418         var options = {
5419             lines: 12,                  // The number of lines to draw
5420             angle: 0.15,                // The length of each line
5421             lineWidth: 0.44,            // 0.44 The line thickness
5422             pointer: {
5423                 length: 0.8,            // 0.9 The radius of the inner circle
5424                 strokeWidth: 0.035,     // The rotation offset
5425                 color: pointerColor     // Fill color
5426             },
5427             colorStart: startColor,     // Colors
5428             colorStop: stopColor,       // just experiment with them
5429             strokeColor: strokeColor,   // to see which ones work best for you
5430             limitMax: true,
5431             generateGradient: (generateGradient === true)?true:false,
5432             gradientType: 0
5433         };
5434
5435         if (generateGradient.constructor === Array) {
5436             // example options:
5437             // data-gauge-generate-gradient="[0, 50, 100]"
5438             // data-gauge-gradient-percent-color-0="#FFFFFF"
5439             // data-gauge-gradient-percent-color-50="#999900"
5440             // data-gauge-gradient-percent-color-100="#000000"
5441
5442             options.percentColors = new Array();
5443             var len = generateGradient.length;
5444             while(len--) {
5445                 var pcent = generateGradient[len];
5446                 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5447                 if(color !== false) {
5448                     var a = new Array();
5449                     a[0] = pcent / 100;
5450                     a[1] = color;
5451                     options.percentColors.unshift(a);
5452                 }
5453             }
5454             if(options.percentColors.length === 0)
5455                 delete options.percentColors;
5456         }
5457         else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5458             options.percentColors = [
5459                 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5460                 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5461                 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5462                 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5463                 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5464                 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5465                 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5466                 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5467                 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5468                 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5469                 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5470         }
5471
5472         state.gauge_canvas = document.createElement('canvas');
5473         state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5474         state.gauge_canvas.className = 'gaugeChart';
5475         state.gauge_canvas.width  = width;
5476         state.gauge_canvas.height = height;
5477         state.element_chart.appendChild(state.gauge_canvas);
5478
5479         var valuefontsize = Math.floor(height / 6);
5480         var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5481         state.gaugeChartLabel = document.createElement('span');
5482         state.gaugeChartLabel.className = 'gaugeChartLabel';
5483         state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5484         state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5485         state.element_chart.appendChild(state.gaugeChartLabel);
5486
5487         var titlefontsize = Math.round(valuefontsize / 2);
5488         var titletop = 0;
5489         state.gaugeChartTitle = document.createElement('span');
5490         state.gaugeChartTitle.className = 'gaugeChartTitle';
5491         state.gaugeChartTitle.innerHTML = state.title;
5492         state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5493         state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5494         state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5495         state.element_chart.appendChild(state.gaugeChartTitle);
5496
5497         var unitfontsize = Math.round(titlefontsize * 0.9);
5498         state.gaugeChartUnits = document.createElement('span');
5499         state.gaugeChartUnits.className = 'gaugeChartUnits';
5500         state.gaugeChartUnits.innerHTML = state.units;
5501         state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5502         state.element_chart.appendChild(state.gaugeChartUnits);
5503
5504         state.gaugeChartMin = document.createElement('span');
5505         state.gaugeChartMin.className = 'gaugeChartMin';
5506         state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5507         state.element_chart.appendChild(state.gaugeChartMin);
5508
5509         state.gaugeChartMax = document.createElement('span');
5510         state.gaugeChartMax.className = 'gaugeChartMax';
5511         state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5512         state.element_chart.appendChild(state.gaugeChartMax);
5513
5514         // when we just re-create the chart
5515         // do not animate the first update
5516         var animate = true;
5517         if(typeof state.gauge_instance !== 'undefined')
5518             animate = false;
5519
5520         state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5521
5522         state.___gaugeOld__ = {
5523             value: value,
5524             min: 0,
5525             max: max,
5526             valueLabel: null,
5527             minLabel: null,
5528             maxLabel: null
5529         };
5530
5531         // we will always feed a percentage
5532         state.gauge_instance.minValue = 0;
5533         state.gauge_instance.maxValue = 100;
5534
5535         NETDATA.gaugeAnimation(state, animate);
5536         NETDATA.gaugeSet(state, value, 0, max);
5537         NETDATA.gaugeSetLabels(state, value, 0, max);
5538         NETDATA.gaugeAnimation(state, true);
5539         return true;
5540     };
5541
5542     // ----------------------------------------------------------------------------------------------------------------
5543     // Charts Libraries Registration
5544
5545     NETDATA.chartLibraries = {
5546         "dygraph": {
5547             initialize: NETDATA.dygraphInitialize,
5548             create: NETDATA.dygraphChartCreate,
5549             update: NETDATA.dygraphChartUpdate,
5550             resize: function(state) {
5551                 if(typeof state.dygraph_instance.resize === 'function')
5552                     state.dygraph_instance.resize();
5553             },
5554             setSelection: NETDATA.dygraphSetSelection,
5555             clearSelection:  NETDATA.dygraphClearSelection,
5556             toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5557             initialized: false,
5558             enabled: true,
5559             format: function(state) { return 'json'; },
5560             options: function(state) { return 'ms|flip'; },
5561             legend: function(state) {
5562                 if(this.isSparkline(state) === false)
5563                     return 'right-side';
5564                 else
5565                     return null;
5566             },
5567             autoresize: function(state) { return true; },
5568             max_updates_to_recreate: function(state) { return 5000; },
5569             track_colors: function(state) { return true; },
5570             pixels_per_point: function(state) {
5571                 if(this.isSparkline(state) === false)
5572                     return 3;
5573                 else
5574                     return 2;
5575             },
5576
5577             isSparkline: function(state) {
5578                 if(typeof state.dygraph_sparkline === 'undefined') {
5579                     var t = $(state.element).data('dygraph-theme');
5580                     if(t === 'sparkline')
5581                         state.dygraph_sparkline = true;
5582                     else
5583                         state.dygraph_sparkline = false;
5584                 }
5585                 return state.dygraph_sparkline;
5586             }
5587         },
5588         "sparkline": {
5589             initialize: NETDATA.sparklineInitialize,
5590             create: NETDATA.sparklineChartCreate,
5591             update: NETDATA.sparklineChartUpdate,
5592             resize: null,
5593             setSelection: undefined, // function(state, t) { return true; },
5594             clearSelection: undefined, // function(state) { return true; },
5595             toolboxPanAndZoom: null,
5596             initialized: false,
5597             enabled: true,
5598             format: function(state) { return 'array'; },
5599             options: function(state) { return 'flip|abs'; },
5600             legend: function(state) { return null; },
5601             autoresize: function(state) { return false; },
5602             max_updates_to_recreate: function(state) { return 5000; },
5603             track_colors: function(state) { return false; },
5604             pixels_per_point: function(state) { return 3; }
5605         },
5606         "peity": {
5607             initialize: NETDATA.peityInitialize,
5608             create: NETDATA.peityChartCreate,
5609             update: NETDATA.peityChartUpdate,
5610             resize: null,
5611             setSelection: undefined, // function(state, t) { return true; },
5612             clearSelection: undefined, // function(state) { return true; },
5613             toolboxPanAndZoom: null,
5614             initialized: false,
5615             enabled: true,
5616             format: function(state) { return 'ssvcomma'; },
5617             options: function(state) { return 'null2zero|flip|abs'; },
5618             legend: function(state) { return null; },
5619             autoresize: function(state) { return false; },
5620             max_updates_to_recreate: function(state) { return 5000; },
5621             track_colors: function(state) { return false; },
5622             pixels_per_point: function(state) { return 3; }
5623         },
5624         "morris": {
5625             initialize: NETDATA.morrisInitialize,
5626             create: NETDATA.morrisChartCreate,
5627             update: NETDATA.morrisChartUpdate,
5628             resize: null,
5629             setSelection: undefined, // function(state, t) { return true; },
5630             clearSelection: undefined, // function(state) { return true; },
5631             toolboxPanAndZoom: null,
5632             initialized: false,
5633             enabled: true,
5634             format: function(state) { return 'json'; },
5635             options: function(state) { return 'objectrows|ms'; },
5636             legend: function(state) { return null; },
5637             autoresize: function(state) { return false; },
5638             max_updates_to_recreate: function(state) { return 50; },
5639             track_colors: function(state) { return false; },
5640             pixels_per_point: function(state) { return 15; }
5641         },
5642         "google": {
5643             initialize: NETDATA.googleInitialize,
5644             create: NETDATA.googleChartCreate,
5645             update: NETDATA.googleChartUpdate,
5646             resize: null,
5647             setSelection: undefined, //function(state, t) { return true; },
5648             clearSelection: undefined, //function(state) { return true; },
5649             toolboxPanAndZoom: null,
5650             initialized: false,
5651             enabled: true,
5652             format: function(state) { return 'datatable'; },
5653             options: function(state) { return ''; },
5654             legend: function(state) { return null; },
5655             autoresize: function(state) { return false; },
5656             max_updates_to_recreate: function(state) { return 300; },
5657             track_colors: function(state) { return false; },
5658             pixels_per_point: function(state) { return 4; }
5659         },
5660         "raphael": {
5661             initialize: NETDATA.raphaelInitialize,
5662             create: NETDATA.raphaelChartCreate,
5663             update: NETDATA.raphaelChartUpdate,
5664             resize: null,
5665             setSelection: undefined, // function(state, t) { return true; },
5666             clearSelection: undefined, // function(state) { return true; },
5667             toolboxPanAndZoom: null,
5668             initialized: false,
5669             enabled: true,
5670             format: function(state) { return 'json'; },
5671             options: function(state) { return ''; },
5672             legend: function(state) { return null; },
5673             autoresize: function(state) { return false; },
5674             max_updates_to_recreate: function(state) { return 5000; },
5675             track_colors: function(state) { return false; },
5676             pixels_per_point: function(state) { return 3; }
5677         },
5678         "c3": {
5679             initialize: NETDATA.c3Initialize,
5680             create: NETDATA.c3ChartCreate,
5681             update: NETDATA.c3ChartUpdate,
5682             resize: null,
5683             setSelection: undefined, // function(state, t) { return true; },
5684             clearSelection: undefined, // function(state) { return true; },
5685             toolboxPanAndZoom: null,
5686             initialized: false,
5687             enabled: true,
5688             format: function(state) { return 'csvjsonarray'; },
5689             options: function(state) { return 'milliseconds'; },
5690             legend: function(state) { return null; },
5691             autoresize: function(state) { return false; },
5692             max_updates_to_recreate: function(state) { return 5000; },
5693             track_colors: function(state) { return false; },
5694             pixels_per_point: function(state) { return 15; }
5695         },
5696         "d3": {
5697             initialize: NETDATA.d3Initialize,
5698             create: NETDATA.d3ChartCreate,
5699             update: NETDATA.d3ChartUpdate,
5700             resize: null,
5701             setSelection: undefined, // function(state, t) { return true; },
5702             clearSelection: undefined, // function(state) { return true; },
5703             toolboxPanAndZoom: null,
5704             initialized: false,
5705             enabled: true,
5706             format: function(state) { return 'json'; },
5707             options: function(state) { return ''; },
5708             legend: function(state) { return null; },
5709             autoresize: function(state) { return false; },
5710             max_updates_to_recreate: function(state) { return 5000; },
5711             track_colors: function(state) { return false; },
5712             pixels_per_point: function(state) { return 3; }
5713         },
5714         "easypiechart": {
5715             initialize: NETDATA.easypiechartInitialize,
5716             create: NETDATA.easypiechartChartCreate,
5717             update: NETDATA.easypiechartChartUpdate,
5718             resize: null,
5719             setSelection: NETDATA.easypiechartSetSelection,
5720             clearSelection: NETDATA.easypiechartClearSelection,
5721             toolboxPanAndZoom: null,
5722             initialized: false,
5723             enabled: true,
5724             format: function(state) { return 'array'; },
5725             options: function(state) { return 'absolute'; },
5726             legend: function(state) { return null; },
5727             autoresize: function(state) { return false; },
5728             max_updates_to_recreate: function(state) { return 5000; },
5729             track_colors: function(state) { return true; },
5730             pixels_per_point: function(state) { return 3; },
5731             aspect_ratio: 100
5732         },
5733         "gauge": {
5734             initialize: NETDATA.gaugeInitialize,
5735             create: NETDATA.gaugeChartCreate,
5736             update: NETDATA.gaugeChartUpdate,
5737             resize: null,
5738             setSelection: NETDATA.gaugeSetSelection,
5739             clearSelection: NETDATA.gaugeClearSelection,
5740             toolboxPanAndZoom: null,
5741             initialized: false,
5742             enabled: true,
5743             format: function(state) { return 'array'; },
5744             options: function(state) { return 'absolute'; },
5745             legend: function(state) { return null; },
5746             autoresize: function(state) { return false; },
5747             max_updates_to_recreate: function(state) { return 5000; },
5748             track_colors: function(state) { return true; },
5749             pixels_per_point: function(state) { return 3; },
5750             aspect_ratio: 70
5751         }
5752     };
5753
5754     NETDATA.registerChartLibrary = function(library, url) {
5755         if(NETDATA.options.debug.libraries === true)
5756             console.log("registering chart library: " + library);
5757
5758         NETDATA.chartLibraries[library].url = url;
5759         NETDATA.chartLibraries[library].initialized = true;
5760         NETDATA.chartLibraries[library].enabled = true;
5761     };
5762
5763     // ----------------------------------------------------------------------------------------------------------------
5764     // Load required JS libraries and CSS
5765
5766     NETDATA.requiredJs = [
5767         {
5768             url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5769             async: false,
5770             isAlreadyLoaded: function() {
5771                 // check if bootstrap is loaded
5772                 if(typeof $().emulateTransitionEnd == 'function')
5773                     return true;
5774                 else {
5775                     if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5776                         return true;
5777                     else
5778                         return false;
5779                 }
5780             }
5781         },
5782         {
5783             url: NETDATA.serverDefault + 'lib/jquery.nanoscroller-0.8.7.min.js',
5784             isAlreadyLoaded: function() { return false; }
5785         }
5786     ];
5787
5788     NETDATA.requiredCSS = [
5789         {
5790             url: NETDATA.themes.current.bootstrap_css,
5791             isAlreadyLoaded: function() {
5792                 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5793                     return true;
5794                 else
5795                     return false;
5796             }
5797         },
5798         {
5799             url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
5800             isAlreadyLoaded: function() { return false; }
5801         },
5802         {
5803             url: NETDATA.themes.current.dashboard_css,
5804             isAlreadyLoaded: function() { return false; }
5805         }
5806     ];
5807
5808     NETDATA.loadedRequiredJs = 0;
5809     NETDATA.loadRequiredJs = function(index, callback) {
5810         if(index >= NETDATA.requiredJs.length) {
5811             if(typeof callback === 'function')
5812                 callback();
5813             return;
5814         }
5815
5816         if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5817             NETDATA.loadedRequiredJs++;
5818             NETDATA.loadRequiredJs(++index, callback);
5819             return;
5820         }
5821
5822         if(NETDATA.options.debug.main_loop === true)
5823             console.log('loading ' + NETDATA.requiredJs[index].url);
5824
5825         var async = true;
5826         if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5827             async = false;
5828
5829         $.ajax({
5830             url: NETDATA.requiredJs[index].url,
5831             cache: true,
5832             dataType: "script",
5833             xhrFields: { withCredentials: true } // required for the cookie
5834         })
5835         .done(function() {
5836             if(NETDATA.options.debug.main_loop === true)
5837                 console.log('loaded ' + NETDATA.requiredJs[index].url);
5838         })
5839         .fail(function() {
5840             alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5841         })
5842         .always(function() {
5843             NETDATA.loadedRequiredJs++;
5844
5845             if(async === false)
5846                 NETDATA.loadRequiredJs(++index, callback);
5847         })
5848
5849         if(async === true)
5850             NETDATA.loadRequiredJs(++index, callback);
5851     };
5852
5853     NETDATA.loadRequiredCSS = function(index) {
5854         if(index >= NETDATA.requiredCSS.length)
5855             return;
5856
5857         if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5858             NETDATA.loadRequiredCSS(++index);
5859             return;
5860         }
5861
5862         if(NETDATA.options.debug.main_loop === true)
5863             console.log('loading ' + NETDATA.requiredCSS[index].url);
5864
5865         NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5866         NETDATA.loadRequiredCSS(++index);
5867     };
5868
5869
5870     // ----------------------------------------------------------------------------------------------------------------
5871     // Registry of netdata hosts
5872
5873     NETDATA.alarms = {
5874         onclick: null,                  // the callback to handle the click - it will be called with the alarm log entry
5875         chart_div_offset: 100,          // give that space above the chart when scrolling to it
5876         chart_div_id_prefix: 'chart_',  // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
5877         chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
5878
5879         ms_penalty: 0,                  // the time penalty of the next alarm
5880         ms_between_notifications: 500,  // firefox moves the alarms off-screen (above, outside the top of the screen)
5881                                         // if alarms are shown faster than: one per 500ms
5882
5883         notifications: false,           // when true, the browser supports notifications (may not be granted though)
5884         last_notification_id: 0,        // the id of the last alarm_log we have raised an alarm for
5885         first_notification_id: 0,       // the id of the first alarm_log entry for this session
5886                                         // this is used to prevent CLEAR notifications for past events
5887         // notifications_shown: new Array(),
5888
5889         server: null,                   // the server to connect to for fetching alarms
5890         current: null,                  // the list of raised alarms - updated in the background
5891         callback: null,                 // a callback function to call every time the list of raised alarms is refreshed
5892
5893         notify: function(entry) {
5894             // console.log('alarm ' + entry.unique_id);
5895
5896             if(entry.updated === true) {
5897                 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
5898                 return;
5899             }
5900
5901             var value = entry.value;
5902             if(NETDATA.alarms.current !== null) {
5903                 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
5904                 if(typeof t !== 'undefined' && entry.status == t.status)
5905                     value = t.value;
5906             }
5907
5908             var name = entry.name.replace(/_/g, ' ');
5909             var status = entry.status.toLowerCase();
5910             var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
5911             var tag = entry.alarm_id;
5912             var icon = 'images/seo-performance-128.png';
5913             var interaction = false;
5914             var data = entry;
5915             var show = true;
5916
5917             // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' +  entry.status);
5918
5919             switch(entry.status) {
5920                 case 'REMOVED':
5921                     show = false;
5922                     break;
5923
5924                 case 'UNDEFINED':
5925                     return;
5926
5927                 case 'UNINITIALIZED':
5928                     return;
5929
5930                 case 'CLEAR':
5931                     if(entry.unique_id < NETDATA.alarms.first_notification_id) {
5932                         // console.log('alarm ' + entry.unique_id + ' is not current');
5933                         return;
5934                     }
5935                     if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
5936                         // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
5937                         return;
5938                     }
5939                     title = name + ' back to normal';
5940                     icon = 'images/check-mark-2-128-green.png'
5941                     interaction = false;
5942                     break;
5943
5944                 case 'WARNING':
5945                     if(entry.old_status === 'CRITICAL')
5946                         status = 'demoted to ' + entry.status.toLowerCase();
5947
5948                     icon = 'images/alert-128-orange.png';
5949                     interaction = false;
5950                     break;
5951
5952                 case 'CRITICAL':
5953                     if(entry.old_status === 'WARNING')
5954                         status = 'escalated to ' + entry.status.toLowerCase();
5955                     
5956                     icon = 'images/alert-128-red.png'
5957                     interaction = true;
5958                     break;
5959
5960                 default:
5961                     console.log('invalid alarm status ' + entry.status);
5962                     return;
5963             }
5964
5965             /*
5966             // cleanup old notifications with the same alarm_id as this one
5967             // FIXME: it does not seem to work on any web browser!
5968             var len = NETDATA.alarms.notifications_shown.length;
5969             while(len--) {
5970                 var n = NETDATA.alarms.notifications_shown[len];
5971                 if(n.data.alarm_id === entry.alarm_id) {
5972                     console.log('removing old alarm ' + n.data.unique_id);
5973
5974                     // close the notification
5975                     n.close.bind(n);
5976
5977                     // remove it from the array
5978                     NETDATA.alarms.notifications_shown.splice(len, 1);
5979                     len = NETDATA.alarms.notifications_shown.length;
5980                 }
5981             }
5982             */
5983
5984             if(show === true) {
5985
5986                 setTimeout(function() {
5987                     // show this notification
5988                     // console.log('new notification: ' + title);
5989                     var n = new Notification(title, {
5990                         body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
5991                         tag: tag,
5992                         requireInteraction: interaction,
5993                         icon: NETDATA.serverDefault + icon,
5994                         data: data
5995                     });
5996
5997                     n.onclick = function(event) {
5998                         event.preventDefault();
5999                         NETDATA.alarms.onclick(event.target.data);
6000                     };
6001
6002                     // console.log(n);
6003                     // NETDATA.alarms.notifications_shown.push(n);
6004                     // console.log(entry);
6005                 }, NETDATA.alarms.ms_penalty);
6006
6007                 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6008             }
6009         },
6010
6011         scrollToChart: function(chart_id) {
6012             if(typeof chart_id === 'string') {
6013                 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6014                 if(typeof offset !== 'undefined') {
6015                     $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6016                     return true;
6017                 }
6018             }
6019             return false;
6020         },
6021
6022         scrollToAlarm: function(alarm) {
6023             if(typeof alarm === 'object') {
6024                 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6025
6026                 if(ret === true && NETDATA.options.page_is_visible === false)
6027                     window.focus();
6028                 //    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.');
6029             }
6030
6031         },
6032
6033         notifyAll: function() {
6034             // console.log('FETCHING ALARM LOG');
6035             NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6036                 // console.log('ALARM LOG FETCHED');
6037
6038                 if(data === null || typeof data !== 'object') {
6039                     console.log('invalid alarms log response');
6040                     return;
6041                 }
6042
6043                 if(data.length === 0) {
6044                     console.log('received empty alarm log');
6045                     return;
6046                 }
6047
6048                 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6049
6050                 data.sort(function(a, b) {
6051                     if(a.unique_id > b.unique_id) return -1;
6052                     if(a.unique_id < b.unique_id) return 1;
6053                     return 0;
6054                 });
6055
6056                 NETDATA.alarms.ms_penalty = 0;
6057
6058                 var len = data.length;
6059                 while(len--) {
6060                     if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6061                         NETDATA.alarms.notify(data[len]);
6062                     }
6063                     //else
6064                     //    console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6065                 }
6066
6067                 NETDATA.alarms.last_notification_id = data[0].unique_id;
6068                 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6069                 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6070             })
6071         },
6072
6073         check_notifications: function() {
6074             // returns true if we should fire 1+ notifications
6075
6076             if(NETDATA.alarms.notifications !== true) {
6077                 // console.log('notifications not available');
6078                 return false;
6079             }
6080
6081             if(Notification.permission !== 'granted') {
6082                 // console.log('notifications not granted');
6083                 return false;
6084             }
6085
6086             if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6087                 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6088
6089                 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6090                     // console.log('new alarms detected');
6091                     return true;
6092                 }
6093                 //else console.log('no new alarms');
6094             }
6095             // else console.log('cannot process alarms');
6096
6097             return false;
6098         },
6099
6100         get: function(what, callback) {
6101             $.ajax({
6102                 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6103                 async: true,
6104                 cache: false,
6105                 headers: {
6106                     'Cache-Control': 'no-cache, no-store',
6107                     'Pragma': 'no-cache'
6108                 },
6109                 xhrFields: { withCredentials: true } // required for the cookie
6110             })
6111                 .done(function(data) {
6112                     if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6113                         NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6114
6115                     if(typeof callback === 'function')
6116                         callback(data);
6117                 })
6118                 .fail(function() {
6119                     NETDATA.error(415, NETDATA.alarms.server);
6120
6121                     if(typeof callback === 'function')
6122                         callback(null);
6123                 });
6124         },
6125
6126         update_forever: function() {
6127             NETDATA.alarms.get('active', function(data) {
6128                 if(data !== null) {
6129                     NETDATA.alarms.current = data;
6130
6131                     if(NETDATA.alarms.check_notifications() === true) {
6132                         NETDATA.alarms.notifyAll();
6133                     }
6134
6135                     if (typeof NETDATA.alarms.callback === 'function') {
6136                         NETDATA.alarms.callback(data);
6137                     }
6138
6139                     // Health monitoring is disabled on this netdata
6140                     if(data.status === false) return;
6141                 }
6142
6143                 setTimeout(NETDATA.alarms.update_forever, 10000);
6144             });
6145         },
6146
6147         get_log: function(last_id, callback) {
6148             // console.log('fetching all log after ' + last_id.toString());
6149             $.ajax({
6150                 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6151                 async: true,
6152                 cache: false,
6153                 headers: {
6154                     'Cache-Control': 'no-cache, no-store',
6155                     'Pragma': 'no-cache'
6156                 },
6157                 xhrFields: { withCredentials: true } // required for the cookie
6158             })
6159                 .done(function(data) {
6160                     if(typeof callback === 'function')
6161                         callback(data);
6162                 })
6163                 .fail(function() {
6164                     NETDATA.error(416, NETDATA.alarms.server);
6165
6166                     if(typeof callback === 'function')
6167                         callback(null);
6168                 });
6169         },
6170
6171         init: function() {
6172             var host = NETDATA.serverDefault;
6173             while(host.slice(-1) === '/')
6174                 host = host.substring(0, host.length - 1);
6175             NETDATA.alarms.server = host;
6176
6177             NETDATA.alarms.last_notification_id = NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6178
6179             if(NETDATA.alarms.onclick === null)
6180                 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6181
6182             if(netdataShowAlarms === true) {
6183                 NETDATA.alarms.update_forever();
6184             
6185                 if('Notification' in window) {
6186                     // console.log('notifications available');
6187                     NETDATA.alarms.notifications = true;
6188
6189                     if(Notification.permission === 'default')
6190                         Notification.requestPermission();
6191                 }
6192             }
6193         }
6194     };
6195
6196     // ----------------------------------------------------------------------------------------------------------------
6197     // Registry of netdata hosts
6198
6199     NETDATA.registry = {
6200         server: null,       // the netdata registry server
6201         person_guid: null,  // the unique ID of this browser / user
6202         machine_guid: null, // the unique ID the netdata server that served dashboard.js
6203         hostname: null,     // the hostname of the netdata server that served dashboard.js
6204         machines: null,         // the user's other URLs
6205         machines_array: null,   // the user's other URLs in an array
6206         person_urls: null,
6207
6208         parsePersonUrls: function(person_urls) {
6209             // console.log(person_urls);
6210             NETDATA.registry.person_urls = person_urls;
6211
6212             if(person_urls) {
6213                 NETDATA.registry.machines = {};
6214                 NETDATA.registry.machines_array = new Array();
6215
6216                 var now = Date.now();
6217                 var apu = person_urls;
6218                 var i = apu.length;
6219                 while(i--) {
6220                     if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6221                         // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6222
6223                         var obj = {
6224                             guid: apu[i][0],
6225                             url: apu[i][1],
6226                             last_t: apu[i][2],
6227                             accesses: apu[i][3],
6228                             name: apu[i][4],
6229                             alternate_urls: new Array()
6230                         };
6231                         obj.alternate_urls.push(apu[i][1]);
6232
6233                         NETDATA.registry.machines[apu[i][0]] = obj;
6234                         NETDATA.registry.machines_array.push(obj);
6235                     }
6236                     else {
6237                         // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6238
6239                         var pu = NETDATA.registry.machines[apu[i][0]];
6240                         if(pu.last_t < apu[i][2]) {
6241                             pu.url = apu[i][1];
6242                             pu.last_t = apu[i][2];
6243                             pu.name = apu[i][4];
6244                         }
6245                         pu.accesses += apu[i][3];
6246                         pu.alternate_urls.push(apu[i][1]);
6247                     }
6248                 }
6249             }
6250
6251             if(typeof netdataRegistryCallback === 'function')
6252                 netdataRegistryCallback(NETDATA.registry.machines_array);
6253         },
6254
6255         init: function() {
6256             if(netdataRegistry !== true) return;
6257
6258             NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6259                 if(data) {
6260                     NETDATA.registry.server = data.registry;
6261                     NETDATA.registry.machine_guid = data.machine_guid;
6262                     NETDATA.registry.hostname = data.hostname;
6263
6264                     NETDATA.registry.access(2, function (person_urls) {
6265                         NETDATA.registry.parsePersonUrls(person_urls);
6266
6267                     });
6268                 }
6269             });
6270         },
6271
6272         hello: function(host, callback) {
6273             while(host.slice(-1) === '/')
6274                 host = host.substring(0, host.length - 1);
6275
6276             // send HELLO to a netdata server:
6277             // 1. verifies the server is reachable
6278             // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6279             $.ajax({
6280                     url: host + '/api/v1/registry?action=hello',
6281                     async: true,
6282                     cache: false,
6283                     headers: {
6284                         'Cache-Control': 'no-cache, no-store',
6285                         'Pragma': 'no-cache'
6286                     },
6287                     xhrFields: { withCredentials: true } // required for the cookie
6288                 })
6289                 .done(function(data) {
6290                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6291                         NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6292                         data = null;
6293                     }
6294
6295                     if(typeof callback === 'function')
6296                         callback(data);
6297                 })
6298                 .fail(function() {
6299                     NETDATA.error(407, host);
6300
6301                     if(typeof callback === 'function')
6302                         callback(null);
6303                 });
6304         },
6305
6306         access: function(max_redirects, callback) {
6307             // send ACCESS to a netdata registry:
6308             // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6309             // 2. it responds with a list of netdata servers we know
6310             // the registry identifies us using a cookie it sets the first time we access it
6311             // the registry may respond with a redirect URL to send us to another registry
6312             $.ajax({
6313                     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),
6314                     async: true,
6315                     cache: false,
6316                     headers: {
6317                         'Cache-Control': 'no-cache, no-store',
6318                         'Pragma': 'no-cache'
6319                     },
6320                     xhrFields: { withCredentials: true } // required for the cookie
6321                 })
6322                 .done(function(data) {
6323                     var redirect = null;
6324                     if(typeof data.registry === 'string')
6325                         redirect = data.registry;
6326
6327                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6328                         NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6329                         data = null;
6330                     }
6331
6332                     if(data === null) {
6333                         if(redirect !== null && max_redirects > 0) {
6334                             NETDATA.registry.server = redirect;
6335                             NETDATA.registry.access(max_redirects - 1, callback);
6336                         }
6337                         else {
6338                             if(typeof callback === 'function')
6339                                 callback(null);
6340                         }
6341                     }
6342                     else {
6343                         if(typeof data.person_guid === 'string')
6344                             NETDATA.registry.person_guid = data.person_guid;
6345
6346                         if(typeof callback === 'function')
6347                             callback(data.urls);
6348                     }
6349                 })
6350                 .fail(function() {
6351                     NETDATA.error(410, NETDATA.registry.server);
6352
6353                     if(typeof callback === 'function')
6354                         callback(null);
6355                 });
6356         },
6357
6358         delete: function(delete_url, callback) {
6359             // send DELETE to a netdata registry:
6360             $.ajax({
6361                 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),
6362                 async: true,
6363                 cache: false,
6364                 headers: {
6365                     'Cache-Control': 'no-cache, no-store',
6366                     'Pragma': 'no-cache'
6367                 },
6368                 xhrFields: { withCredentials: true } // required for the cookie
6369             })
6370                 .done(function(data) {
6371                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6372                         NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6373                         data = null;
6374                     }
6375
6376                     if(typeof callback === 'function')
6377                         callback(data);
6378                 })
6379                 .fail(function() {
6380                     NETDATA.error(412, NETDATA.registry.server);
6381
6382                     if(typeof callback === 'function')
6383                         callback(null);
6384                 });
6385         },
6386
6387         search: function(machine_guid, callback) {
6388             // SEARCH for the URLs of a machine:
6389             $.ajax({
6390                 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,
6391                 async: true,
6392                 cache: false,
6393                 headers: {
6394                     'Cache-Control': 'no-cache, no-store',
6395                     'Pragma': 'no-cache'
6396                 },
6397                 xhrFields: { withCredentials: true } // required for the cookie
6398             })
6399                 .done(function(data) {
6400                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6401                         NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6402                         data = null;
6403                     }
6404
6405                     if(typeof callback === 'function')
6406                         callback(data);
6407                 })
6408                 .fail(function() {
6409                     NETDATA.error(418, NETDATA.registry.server);
6410
6411                     if(typeof callback === 'function')
6412                         callback(null);
6413                 });
6414         },
6415
6416         switch: function(new_person_guid, callback) {
6417             // impersonate
6418             $.ajax({
6419                 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,
6420                 async: true,
6421                 cache: false,
6422                 headers: {
6423                     'Cache-Control': 'no-cache, no-store',
6424                     'Pragma': 'no-cache'
6425                 },
6426                 xhrFields: { withCredentials: true } // required for the cookie
6427             })
6428                 .done(function(data) {
6429                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6430                         NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6431                         data = null;
6432                     }
6433
6434                     if(typeof callback === 'function')
6435                         callback(data);
6436                 })
6437                 .fail(function() {
6438                     NETDATA.error(414, NETDATA.registry.server);
6439
6440                     if(typeof callback === 'function')
6441                         callback(null);
6442                 });
6443         }
6444     };
6445
6446     // ----------------------------------------------------------------------------------------------------------------
6447     // Boot it!
6448
6449     if(typeof netdataPrepCallback === 'function')
6450         netdataPrepCallback();
6451
6452     NETDATA.errorReset();
6453     NETDATA.loadRequiredCSS(0);
6454
6455     NETDATA._loadjQuery(function() {
6456         NETDATA.loadRequiredJs(0, function() {
6457             if(typeof $().emulateTransitionEnd !== 'function') {
6458                 // bootstrap is not available
6459                 NETDATA.options.current.show_help = false;
6460             }
6461
6462             if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6463                 if(NETDATA.options.debug.main_loop === true)
6464                     console.log('starting chart refresh thread');
6465
6466                 NETDATA.start();
6467             }
6468         });
6469     });
6470
6471     // window.NETDATA = NETDATA;
6472 // })(window, document);