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