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