]> arthur.barton.de Git - netdata.git/blob - web/dashboard.js
Merge pull request #1451 from ktsaou/master
[netdata.git] / web / dashboard.js
1 // You can set the following variables before loading this script:
2 //
3 // var netdataNoDygraphs = true;        // do not use dygraph
4 // var netdataNoSparklines = true;      // do not use sparkline
5 // var netdataNoPeitys = true;          // do not use peity
6 // var netdataNoGoogleCharts = true;    // do not use google
7 // var netdataNoMorris = true;          // do not use morris
8 // var netdataNoEasyPieChart = true;    // do not use easy pie chart
9 // var netdataNoGauge = true;           // do not use gauge.js
10 // var netdataNoD3 = true;              // do not use D3
11 // var netdataNoC3 = true;              // do not use C3
12 // var netdataNoBootstrap = true;       // do not load bootstrap
13 // var netdataDontStart = true;         // do not start the thread to process the charts
14 // var netdataErrorCallback = null;     // Callback function that will be invoked upon error
15 // var netdataRegistry = true;          // Update the registry (default disabled)
16 // var netdataRegistryCallback = null;  // Callback function that will be invoked with one param,
17 //                                         the URLs from the registry
18 // var netdataShowHelp = false;         // enable/disable help (default enabled)
19 // var netdataShowAlarms = true;        // enable/disable alarms checks and notifications (default disabled)
20 //
21 // var netdataRegistryAfterMs = 1500    // the time to consult to registry on startup
22 //
23 // var netdataCallback = null;          // a function to call when netdata is ready
24 //                                      // netdata will be running while this is called (call NETDATA.pause to stop it)
25 // var netdataPrepCallback = null;      // a callback to be called before netdata does anything else
26 //
27 // You can also set the default netdata server, using the following.
28 // When this variable is not set, we assume the page is hosted on your
29 // netdata server already.
30 // var netdataServer = "http://yourhost:19999"; // set your NetData server
31
32 //(function(window, document, undefined) {
33
34     // ------------------------------------------------------------------------
35     // compatibility fixes
36
37     // fix IE issue with console
38     if(!window.console) { window.console = { log: function(){} }; }
39
40     // if string.endsWith is not defined, define it
41     if(typeof String.prototype.endsWith !== 'function') {
42         String.prototype.endsWith = function(s) {
43             if(s.length > this.length) return false;
44             return this.slice(-s.length) === s;
45         };
46     }
47
48     // if string.startsWith is not defined, define it
49     if(typeof String.prototype.startsWith !== 'function') {
50         String.prototype.startsWith = function(s) {
51             if(s.length > this.length) return false;
52             return this.slice(s.length) === s;
53         };
54     }
55
56     // global namespace
57     var NETDATA = window.NETDATA || {};
58
59     NETDATA.name2id = function(s) {
60         return s
61             .replace(/ /g, '_')
62             .replace(/\(/g, '_')
63             .replace(/\)/g, '_')
64             .replace(/\./g, '_')
65             .replace(/\//g, '_');
66     };
67
68     // ----------------------------------------------------------------------------------------------------------------
69     // Detect the netdata server
70
71     // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
72     // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
73     NETDATA._scriptSource = function() {
74         var script = null;
75
76         if(typeof document.currentScript !== 'undefined') {
77             script = document.currentScript;
78         }
79         else {
80             var all_scripts = document.getElementsByTagName('script');
81             script = all_scripts[all_scripts.length - 1];
82         }
83
84         if (typeof script.getAttribute.length !== 'undefined')
85             script = script.src;
86         else
87             script = script.getAttribute('src', -1);
88
89         return script;
90     };
91
92     if(typeof netdataServer !== 'undefined')
93         NETDATA.serverDefault = netdataServer;
94     else {
95         var s = NETDATA._scriptSource();
96         if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
97         else {
98             console.log('WARNING: Cannot detect the URL of the netdata server.');
99             NETDATA.serverDefault = null;
100         }
101     }
102
103     if(NETDATA.serverDefault === null)
104         NETDATA.serverDefault = '';
105     else if(NETDATA.serverDefault.slice(-1) !== '/')
106         NETDATA.serverDefault += '/';
107
108     // default URLs for all the external files we need
109     // make them RELATIVE so that the whole thing can also be
110     // installed under a web server
111     NETDATA.jQuery              = NETDATA.serverDefault + 'lib/jquery-2.2.4.min.js';
112     NETDATA.peity_js            = NETDATA.serverDefault + 'lib/jquery.peity-3.2.0.min.js';
113     NETDATA.sparkline_js        = NETDATA.serverDefault + 'lib/jquery.sparkline-2.1.2.min.js';
114     NETDATA.easypiechart_js     = NETDATA.serverDefault + 'lib/jquery.easypiechart-97b5824.min.js';
115     NETDATA.gauge_js            = NETDATA.serverDefault + 'lib/gauge-d5260c3.min.js';
116     NETDATA.dygraph_js          = NETDATA.serverDefault + 'lib/dygraph-combined-dd74404.js';
117     NETDATA.dygraph_smooth_js   = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter-dd74404.js';
118     NETDATA.raphael_js          = NETDATA.serverDefault + 'lib/raphael-2.2.4-min.js';
119     NETDATA.c3_js               = NETDATA.serverDefault + 'lib/c3-0.4.11.min.js';
120     NETDATA.c3_css              = NETDATA.serverDefault + 'css/c3-0.4.11.min.css';
121     NETDATA.d3_js               = NETDATA.serverDefault + 'lib/d3-3.5.17.min.js';
122     NETDATA.morris_js           = NETDATA.serverDefault + 'lib/morris-0.5.1.min.js';
123     NETDATA.morris_css          = NETDATA.serverDefault + 'css/morris-0.5.1.css';
124     NETDATA.google_js           = 'https://www.google.com/jsapi';
125
126     NETDATA.themes = {
127         white: {
128             bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-3.3.7.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.sparkline_options = {
3907             type: type,
3908             lineColor: lineColor,
3909             fillColor: fillColor,
3910             chartRangeMin: chartRangeMin,
3911             chartRangeMax: chartRangeMax,
3912             composite: composite,
3913             enableTagOptions: enableTagOptions,
3914             tagOptionPrefix: tagOptionPrefix,
3915             tagValuesAttribute: tagValuesAttribute,
3916             disableHiddenCheck: disableHiddenCheck,
3917             defaultPixelsPerValue: defaultPixelsPerValue,
3918             spotColor: spotColor,
3919             minSpotColor: minSpotColor,
3920             maxSpotColor: maxSpotColor,
3921             spotRadius: spotRadius,
3922             valueSpots: valueSpots,
3923             highlightSpotColor: highlightSpotColor,
3924             highlightLineColor: highlightLineColor,
3925             lineWidth: lineWidth,
3926             normalRangeMin: normalRangeMin,
3927             normalRangeMax: normalRangeMax,
3928             drawNormalOnTop: drawNormalOnTop,
3929             xvalues: xvalues,
3930             chartRangeClip: chartRangeClip,
3931             chartRangeMinX: chartRangeMinX,
3932             chartRangeMaxX: chartRangeMaxX,
3933             disableInteraction: disableInteraction,
3934             disableTooltips: disableTooltips,
3935             disableHighlight: disableHighlight,
3936             highlightLighten: highlightLighten,
3937             highlightColor: highlightColor,
3938             tooltipContainer: tooltipContainer,
3939             tooltipClassname: tooltipClassname,
3940             tooltipChartTitle: state.title,
3941             tooltipFormat: tooltipFormat,
3942             tooltipPrefix: tooltipPrefix,
3943             tooltipSuffix: tooltipSuffix,
3944             tooltipSkipNull: tooltipSkipNull,
3945             tooltipValueLookups: tooltipValueLookups,
3946             tooltipFormatFieldlist: tooltipFormatFieldlist,
3947             tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3948             numberFormatter: numberFormatter,
3949             numberDigitGroupSep: numberDigitGroupSep,
3950             numberDecimalMark: numberDecimalMark,
3951             numberDigitGroupCount: numberDigitGroupCount,
3952             animatedZooms: animatedZooms,
3953             width: state.chartWidth(),
3954             height: state.chartHeight()
3955         };
3956
3957         $(state.element_chart).sparkline(data.result, state.sparkline_options);
3958         return true;
3959     };
3960
3961     // ----------------------------------------------------------------------------------------------------------------
3962     // dygraph
3963
3964     NETDATA.dygraph = {
3965         smooth: false
3966     };
3967
3968     NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3969         if(after < state.netdata_first)
3970             after = state.netdata_first;
3971
3972         if(before > state.netdata_last)
3973             before = state.netdata_last;
3974
3975         state.setMode('zoom');
3976         state.globalSelectionSyncStop();
3977         state.globalSelectionSyncDelay();
3978         state.dygraph_user_action = true;
3979         state.dygraph_force_zoom = true;
3980         state.updateChartPanOrZoom(after, before);
3981         NETDATA.globalPanAndZoom.setMaster(state, after, before);
3982     };
3983
3984     NETDATA.dygraphSetSelection = function(state, t) {
3985         if(typeof state.dygraph_instance !== 'undefined') {
3986             var r = state.calculateRowForTime(t);
3987             if(r !== -1)
3988                 state.dygraph_instance.setSelection(r);
3989             else {
3990                 state.dygraph_instance.clearSelection();
3991                 state.legendShowUndefined();
3992             }
3993         }
3994
3995         return true;
3996     };
3997
3998     NETDATA.dygraphClearSelection = function(state, t) {
3999         if(typeof state.dygraph_instance !== 'undefined') {
4000             state.dygraph_instance.clearSelection();
4001         }
4002         return true;
4003     };
4004
4005     NETDATA.dygraphSmoothInitialize = function(callback) {
4006         $.ajax({
4007             url: NETDATA.dygraph_smooth_js,
4008             cache: true,
4009             dataType: "script",
4010             xhrFields: { withCredentials: true } // required for the cookie
4011         })
4012         .done(function() {
4013             NETDATA.dygraph.smooth = true;
4014             smoothPlotter.smoothing = 0.3;
4015         })
4016         .fail(function() {
4017             NETDATA.dygraph.smooth = false;
4018         })
4019         .always(function() {
4020             if(typeof callback === "function")
4021                 callback();
4022         });
4023     };
4024
4025     NETDATA.dygraphInitialize = function(callback) {
4026         if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
4027             $.ajax({
4028                 url: NETDATA.dygraph_js,
4029                 cache: true,
4030                 dataType: "script",
4031                 xhrFields: { withCredentials: true } // required for the cookie
4032             })
4033             .done(function() {
4034                 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
4035             })
4036             .fail(function() {
4037                 NETDATA.chartLibraries.dygraph.enabled = false;
4038                 NETDATA.error(100, NETDATA.dygraph_js);
4039             })
4040             .always(function() {
4041                 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4042                     NETDATA.dygraphSmoothInitialize(callback);
4043                 else if(typeof callback === "function")
4044                     callback();
4045             });
4046         }
4047         else {
4048             NETDATA.chartLibraries.dygraph.enabled = false;
4049             if(typeof callback === "function")
4050                 callback();
4051         }
4052     };
4053
4054     NETDATA.dygraphChartUpdate = function(state, data) {
4055         var dygraph = state.dygraph_instance;
4056
4057         if(typeof dygraph === 'undefined')
4058             return NETDATA.dygraphChartCreate(state, data);
4059
4060         // when the chart is not visible, and hidden
4061         // if there is a window resize, dygraph detects
4062         // its element size as 0x0.
4063         // this will make it re-appear properly
4064
4065         if(state.tm.last_unhidden > state.dygraph_last_rendered)
4066             dygraph.resize();
4067
4068         var options = {
4069                 file: data.result.data,
4070                 colors: state.chartColors(),
4071                 labels: data.result.labels,
4072                 labelsDivWidth: state.chartWidth() - 70,
4073                 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4074         };
4075
4076         if(state.dygraph_force_zoom === true) {
4077             if(NETDATA.options.debug.dygraph === true || state.debug === true)
4078                 state.log('dygraphChartUpdate() forced zoom update');
4079
4080             options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4081             options.isZoomedIgnoreProgrammaticZoom = true;
4082             state.dygraph_force_zoom = false;
4083         }
4084         else if(state.current.name !== 'auto') {
4085             if(NETDATA.options.debug.dygraph === true || state.debug === true)
4086                 state.log('dygraphChartUpdate() loose update');
4087         }
4088         else {
4089             if(NETDATA.options.debug.dygraph === true || state.debug === true)
4090                 state.log('dygraphChartUpdate() strict update');
4091
4092             options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4093             options.isZoomedIgnoreProgrammaticZoom = true;
4094         }
4095
4096         options.valueRange = state.dygraph_options.valueRange;
4097
4098         var oldMax = null, oldMin = null;
4099         if(state.__commonMin !== null) {
4100             state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4101             oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4102         }
4103         if(state.__commonMax !== null) {
4104             state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4105             oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4106         }
4107
4108         if(state.dygraph_smooth_eligible === true) {
4109             if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4110                 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4111                 NETDATA.dygraphChartCreate(state, data);
4112                 return;
4113             }
4114         }
4115
4116         dygraph.updateOptions(options);
4117
4118         var redraw = false;
4119         if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4120             state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4121             options.valueRange[0] = NETDATA.commonMin.get(state);
4122             redraw = true;
4123         }
4124         if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4125             state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4126             options.valueRange[1] = NETDATA.commonMax.get(state);
4127             redraw = true;
4128         }
4129
4130         if(redraw === true) {
4131             // state.log('forcing redraw to adapt to common- min/max');
4132             dygraph.updateOptions(options);
4133         }
4134
4135         state.dygraph_last_rendered = Date.now();
4136         return true;
4137     };
4138
4139     NETDATA.dygraphChartCreate = function(state, data) {
4140         if(NETDATA.options.debug.dygraph === true || state.debug === true)
4141             state.log('dygraphChartCreate()');
4142
4143         var self = $(state.element);
4144
4145         var chart_type = state.chart.chart_type;
4146         if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4147         chart_type = self.data('dygraph-type') || chart_type;
4148
4149         var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
4150         smooth = self.data('dygraph-smooth') || smooth;
4151
4152         if(NETDATA.dygraph.smooth === false)
4153             smooth = false;
4154
4155         var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
4156         var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
4157
4158         state.dygraph_options = {
4159             colors: self.data('dygraph-colors') || state.chartColors(),
4160
4161             // leave a few pixels empty on the right of the chart
4162             rightGap: self.data('dygraph-rightgap') || 5,
4163             showRangeSelector: self.data('dygraph-showrangeselector') || false,
4164             showRoller: self.data('dygraph-showroller') || false,
4165
4166             title: self.data('dygraph-title') || state.title,
4167             titleHeight: self.data('dygraph-titleheight') || 19,
4168
4169             legend: self.data('dygraph-legend') || 'always', // we need this to get selection events
4170             labels: data.result.labels,
4171             labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
4172             labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
4173             labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
4174             labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
4175             labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
4176             labelsKMB: false,
4177             labelsKMG2: false,
4178             showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
4179             hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
4180
4181             includeZero: self.data('dygraph-includezero') || ((chart_type === 'stacked')? true : false),
4182             xRangePad: self.data('dygraph-xrangepad') || 0,
4183             yRangePad: self.data('dygraph-yrangepad') || 1,
4184
4185             valueRange: self.data('dygraph-valuerange') || [ null, null ],
4186
4187             ylabel: state.units,
4188             yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
4189
4190             // the function to plot the chart
4191             plotter: null,
4192
4193             // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
4194             strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
4195             strokePattern: self.data('dygraph-strokepattern') || undefined,
4196
4197             // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
4198             // i.e. there is a missing point on either side of it. This also controls the size of those dots.
4199             drawPoints: self.data('dygraph-drawpoints') || false,
4200
4201             // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
4202             drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
4203
4204             connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
4205             pointSize: self.data('dygraph-pointsize') || 1,
4206
4207             // enabling this makes the chart with little square lines
4208             stepPlot: self.data('dygraph-stepplot') || false,
4209
4210             // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
4211             strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
4212             strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
4213
4214             fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
4215             fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
4216             stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
4217             stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
4218
4219             drawAxis: self.data('dygraph-drawaxis') || true,
4220             axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
4221             axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
4222             axisLineWidth: self.data('dygraph-axislinewidth') || 1.0,
4223
4224             drawGrid: self.data('dygraph-drawgrid') || true,
4225             gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4226             gridLineWidth: self.data('dygraph-gridlinewidth') || 1.0,
4227             gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4228
4229             maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4230             sigFigs: self.data('dygraph-sigfigs') || null,
4231             digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4232             valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4233
4234             highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4235             highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4236             highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4237
4238             pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4239             visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4240             axes: {
4241                 x: {
4242                     pixelsPerLabel: 50,
4243                     ticker: Dygraph.dateTicker,
4244                     axisLabelFormatter: function (d, gran) {
4245                         return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4246                     },
4247                     valueFormatter: function (ms) {
4248                         //var d = new Date(ms);
4249                         //return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4250
4251                         // no need to return anything here
4252                         return ' ';
4253
4254                     }
4255                 },
4256                 y: {
4257                     pixelsPerLabel: 15,
4258                     valueFormatter: function (x) {
4259                         // we format legends with the state object
4260                         // no need to do anything here
4261                         // return (Math.round(x*100) / 100).toLocaleString();
4262                         // return state.legendFormatValue(x);
4263                         return x;
4264                     }
4265                 }
4266             },
4267             legendFormatter: function(data) {
4268                 var elements = state.element_legend_childs;
4269
4270                 // if the hidden div is not there
4271                 // we are not managing the legend
4272                 if(elements.hidden === null) return;
4273
4274                 if (typeof data.x !== 'undefined') {
4275                     state.legendSetDate(data.x);
4276                     var i = data.series.length;
4277                     while(i--) {
4278                         var series = data.series[i];
4279                         if(series.isVisible === true)
4280                             state.legendSetLabelValue(series.label, series.y);
4281                     }
4282                 }
4283
4284                 return '';
4285             },
4286             drawCallback: function(dygraph, is_initial) {
4287                 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4288                     state.dygraph_user_action = false;
4289
4290                     var x_range = dygraph.xAxisRange();
4291                     var after = Math.round(x_range[0]);
4292                     var before = Math.round(x_range[1]);
4293
4294                     if(NETDATA.options.debug.dygraph === true)
4295                         state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4296
4297                     if(before <= state.netdata_last && after >= state.netdata_first)
4298                         state.updateChartPanOrZoom(after, before);
4299                 }
4300             },
4301             zoomCallback: function(minDate, maxDate, yRanges) {
4302                 if(NETDATA.options.debug.dygraph === true)
4303                     state.log('dygraphZoomCallback()');
4304
4305                 state.globalSelectionSyncStop();
4306                 state.globalSelectionSyncDelay();
4307                 state.setMode('zoom');
4308
4309                 // refresh it to the greatest possible zoom level
4310                 state.dygraph_user_action = true;
4311                 state.dygraph_force_zoom = true;
4312                 state.updateChartPanOrZoom(minDate, maxDate);
4313             },
4314             highlightCallback: function(event, x, points, row, seriesName) {
4315                 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4316                     state.log('dygraphHighlightCallback()');
4317
4318                 state.pauseChart();
4319
4320                 // there is a bug in dygraph when the chart is zoomed enough
4321                 // the time it thinks is selected is wrong
4322                 // here we calculate the time t based on the row number selected
4323                 // which is ok
4324                 var t = state.data_after + row * state.data_update_every;
4325                 // 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);
4326
4327                 state.globalSelectionSync(x);
4328
4329                 // fix legend zIndex using the internal structures of dygraph legend module
4330                 // this works, but it is a hack!
4331                 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4332             },
4333             unhighlightCallback: function(event) {
4334                 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4335                     state.log('dygraphUnhighlightCallback()');
4336
4337                 state.unpauseChart();
4338                 state.globalSelectionSyncStop();
4339             },
4340             interactionModel : {
4341                 mousedown: function(event, dygraph, context) {
4342                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4343                         state.log('interactionModel.mousedown()');
4344
4345                     state.dygraph_user_action = true;
4346                     state.globalSelectionSyncStop();
4347
4348                     if(NETDATA.options.debug.dygraph === true)
4349                         state.log('dygraphMouseDown()');
4350
4351                     // Right-click should not initiate a zoom.
4352                     if(event.button && event.button === 2) return;
4353
4354                     context.initializeMouseDown(event, dygraph, context);
4355
4356                     if(event.button && event.button === 1) {
4357                         if (event.altKey || event.shiftKey) {
4358                             state.setMode('pan');
4359                             state.globalSelectionSyncDelay();
4360                             Dygraph.startPan(event, dygraph, context);
4361                         }
4362                         else {
4363                             state.setMode('zoom');
4364                             state.globalSelectionSyncDelay();
4365                             Dygraph.startZoom(event, dygraph, context);
4366                         }
4367                     }
4368                     else {
4369                         if (event.altKey || event.shiftKey) {
4370                             state.setMode('zoom');
4371                             state.globalSelectionSyncDelay();
4372                             Dygraph.startZoom(event, dygraph, context);
4373                         }
4374                         else {
4375                             state.setMode('pan');
4376                             state.globalSelectionSyncDelay();
4377                             Dygraph.startPan(event, dygraph, context);
4378                         }
4379                     }
4380                 },
4381                 mousemove: function(event, dygraph, context) {
4382                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4383                         state.log('interactionModel.mousemove()');
4384
4385                     if(context.isPanning) {
4386                         state.dygraph_user_action = true;
4387                         state.globalSelectionSyncStop();
4388                         state.globalSelectionSyncDelay();
4389                         state.setMode('pan');
4390                         context.is2DPan = false;
4391                         Dygraph.movePan(event, dygraph, context);
4392                     }
4393                     else if(context.isZooming) {
4394                         state.dygraph_user_action = true;
4395                         state.globalSelectionSyncStop();
4396                         state.globalSelectionSyncDelay();
4397                         state.setMode('zoom');
4398                         Dygraph.moveZoom(event, dygraph, context);
4399                     }
4400                 },
4401                 mouseup: function(event, dygraph, context) {
4402                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4403                         state.log('interactionModel.mouseup()');
4404
4405                     if (context.isPanning) {
4406                         state.dygraph_user_action = true;
4407                         state.globalSelectionSyncDelay();
4408                         Dygraph.endPan(event, dygraph, context);
4409                     }
4410                     else if (context.isZooming) {
4411                         state.dygraph_user_action = true;
4412                         state.globalSelectionSyncDelay();
4413                         Dygraph.endZoom(event, dygraph, context);
4414                     }
4415                 },
4416                 click: function(event, dygraph, context) {
4417                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4418                         state.log('interactionModel.click()');
4419
4420                     event.preventDefault();
4421                 },
4422                 dblclick: function(event, dygraph, context) {
4423                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4424                         state.log('interactionModel.dblclick()');
4425                     NETDATA.resetAllCharts(state);
4426                 },
4427                 wheel: function(event, dygraph, context) {
4428                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4429                         state.log('interactionModel.wheel()');
4430
4431                     // Take the offset of a mouse event on the dygraph canvas and
4432                     // convert it to a pair of percentages from the bottom left.
4433                     // (Not top left, bottom is where the lower value is.)
4434                     function offsetToPercentage(g, offsetX, offsetY) {
4435                         // This is calculating the pixel offset of the leftmost date.
4436                         var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4437                         var yar0 = g.yAxisRange(0);
4438
4439                         // This is calculating the pixel of the higest value. (Top pixel)
4440                         var yOffset = g.toDomCoords(null, yar0[1])[1];
4441
4442                         // x y w and h are relative to the corner of the drawing area,
4443                         // so that the upper corner of the drawing area is (0, 0).
4444                         var x = offsetX - xOffset;
4445                         var y = offsetY - yOffset;
4446
4447                         // This is computing the rightmost pixel, effectively defining the
4448                         // width.
4449                         var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4450
4451                         // This is computing the lowest pixel, effectively defining the height.
4452                         var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4453
4454                         // Percentage from the left.
4455                         var xPct = w === 0 ? 0 : (x / w);
4456                         // Percentage from the top.
4457                         var yPct = h === 0 ? 0 : (y / h);
4458
4459                         // The (1-) part below changes it from "% distance down from the top"
4460                         // to "% distance up from the bottom".
4461                         return [xPct, (1-yPct)];
4462                     }
4463
4464                     // Adjusts [x, y] toward each other by zoomInPercentage%
4465                     // Split it so the left/bottom axis gets xBias/yBias of that change and
4466                     // tight/top gets (1-xBias)/(1-yBias) of that change.
4467                     //
4468                     // If a bias is missing it splits it down the middle.
4469                     function zoomRange(g, zoomInPercentage, xBias, yBias) {
4470                         xBias = xBias || 0.5;
4471                         yBias = yBias || 0.5;
4472
4473                         function adjustAxis(axis, zoomInPercentage, bias) {
4474                             var delta = axis[1] - axis[0];
4475                             var increment = delta * zoomInPercentage;
4476                             var foo = [increment * bias, increment * (1-bias)];
4477
4478                             return [ axis[0] + foo[0], axis[1] - foo[1] ];
4479                         }
4480
4481                         var yAxes = g.yAxisRanges();
4482                         var newYAxes = [];
4483                         for (var i = 0; i < yAxes.length; i++) {
4484                             newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4485                         }
4486
4487                         return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4488                     }
4489
4490                     if(event.altKey || event.shiftKey) {
4491                         state.dygraph_user_action = true;
4492
4493                         state.globalSelectionSyncStop();
4494                         state.globalSelectionSyncDelay();
4495
4496                         // http://dygraphs.com/gallery/interaction-api.js
4497                         var normal_def;
4498                         if(typeof event.wheelDelta === 'number' && event.wheelDelta != NaN)
4499                             // chrome
4500                             normal_def = event.wheelDelta / 40;
4501                         else
4502                             // firefox
4503                             normal_def = event.deltaY * -1.2;
4504
4505                         var normal = (event.detail) ? event.detail * -1 : normal_def;
4506                         var percentage = normal / 50;
4507
4508                         if (!(event.offsetX && event.offsetY)){
4509                             event.offsetX = event.layerX - event.target.offsetLeft;
4510                             event.offsetY = event.layerY - event.target.offsetTop;
4511                         }
4512
4513                         var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4514                         var xPct = percentages[0];
4515                         var yPct = percentages[1];
4516
4517                         var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4518                         var after = new_x_range[0];
4519                         var before = new_x_range[1];
4520
4521                         var first = state.netdata_first + state.data_update_every;
4522                         var last = state.netdata_last + state.data_update_every;
4523
4524                         if(before > last) {
4525                             after -= (before - last);
4526                             before = last;
4527                         }
4528                         if(after < first) {
4529                             after = first;
4530                         }
4531
4532                         state.setMode('zoom');
4533                         if(state.updateChartPanOrZoom(after, before) === true)
4534                             dygraph.updateOptions({ dateWindow: [ after, before ] });
4535
4536                         event.preventDefault();
4537                     }
4538                 },
4539                 touchstart: function(event, dygraph, context) {
4540                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4541                         state.log('interactionModel.touchstart()');
4542
4543                     state.dygraph_user_action = true;
4544                     state.setMode('zoom');
4545                     state.pauseChart();
4546
4547                     Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4548
4549                     // we overwrite the touch directions at the end, to overwrite
4550                     // the internal default of dygraphs
4551                     context.touchDirections = { x: true, y: false };
4552
4553                     state.dygraph_last_touch_start = Date.now();
4554                     state.dygraph_last_touch_move = 0;
4555
4556                     if(typeof event.touches[0].pageX === 'number')
4557                         state.dygraph_last_touch_page_x = event.touches[0].pageX;
4558                     else
4559                         state.dygraph_last_touch_page_x = 0;
4560                 },
4561                 touchmove: function(event, dygraph, context) {
4562                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4563                         state.log('interactionModel.touchmove()');
4564
4565                     state.dygraph_user_action = true;
4566                     Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4567
4568                     state.dygraph_last_touch_move = Date.now();
4569                 },
4570                 touchend: function(event, dygraph, context) {
4571                     if(NETDATA.options.debug.dygraph === true || state.debug === true)
4572                         state.log('interactionModel.touchend()');
4573
4574                     state.dygraph_user_action = true;
4575                     Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4576
4577                     // if it didn't move, it is a selection
4578                     if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4579                         // internal api of dygraphs
4580                         var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4581                         var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4582                         if(NETDATA.dygraphSetSelection(state, t) === true)
4583                             state.globalSelectionSync(t);
4584                     }
4585
4586                     // if it was double tap within double click time, reset the charts
4587                     var now = Date.now();
4588                     if(typeof state.dygraph_last_touch_end !== 'undefined') {
4589                         if(state.dygraph_last_touch_move === 0) {
4590                             var dt = now - state.dygraph_last_touch_end;
4591                             if(dt <= NETDATA.options.current.double_click_speed)
4592                                 NETDATA.resetAllCharts(state);
4593                         }
4594                     }
4595
4596                     // remember the timestamp of the last touch end
4597                     state.dygraph_last_touch_end = now;
4598                 }
4599             }
4600         };
4601
4602         if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4603             state.dygraph_options.drawGrid = false;
4604             state.dygraph_options.drawAxis = false;
4605             state.dygraph_options.title = undefined;
4606             state.dygraph_options.ylabel = undefined;
4607             state.dygraph_options.yLabelWidth = 0;
4608             state.dygraph_options.labelsDivWidth = 120;
4609             state.dygraph_options.labelsDivStyles.width = '120px';
4610             state.dygraph_options.labelsSeparateLines = true;
4611             state.dygraph_options.rightGap = 0;
4612             state.dygraph_options.yRangePad = 1;
4613         }
4614
4615         if(smooth === true) {
4616             state.dygraph_smooth_eligible = true;
4617
4618             if(NETDATA.options.current.smooth_plot === true)
4619                 state.dygraph_options.plotter = smoothPlotter;
4620         }
4621         else state.dygraph_smooth_eligible = false;
4622
4623         state.dygraph_instance = new Dygraph(state.element_chart,
4624             data.result.data, state.dygraph_options);
4625
4626         state.dygraph_force_zoom = false;
4627         state.dygraph_user_action = false;
4628         state.dygraph_last_rendered = Date.now();
4629
4630         if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4631             state.__commonMin = self.data('common-min') || null;
4632             state.__commonMax = self.data('common-max') || null;
4633         }
4634         else {
4635             state.log('incompatible version of dygraphs detected');
4636             state.__commonMin = null;
4637             state.__commonMax = null;
4638         }
4639
4640         return true;
4641     };
4642
4643     // ----------------------------------------------------------------------------------------------------------------
4644     // morris
4645
4646     NETDATA.morrisInitialize = function(callback) {
4647         if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4648
4649             // morris requires raphael
4650             if(!NETDATA.chartLibraries.raphael.initialized) {
4651                 if(NETDATA.chartLibraries.raphael.enabled) {
4652                     NETDATA.raphaelInitialize(function() {
4653                         NETDATA.morrisInitialize(callback);
4654                     });
4655                 }
4656                 else {
4657                     NETDATA.chartLibraries.morris.enabled = false;
4658                     if(typeof callback === "function")
4659                         callback();
4660                 }
4661             }
4662             else {
4663                 NETDATA._loadCSS(NETDATA.morris_css);
4664
4665                 $.ajax({
4666                     url: NETDATA.morris_js,
4667                     cache: true,
4668                     dataType: "script",
4669                     xhrFields: { withCredentials: true } // required for the cookie
4670                 })
4671                 .done(function() {
4672                     NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4673                 })
4674                 .fail(function() {
4675                     NETDATA.chartLibraries.morris.enabled = false;
4676                     NETDATA.error(100, NETDATA.morris_js);
4677                 })
4678                 .always(function() {
4679                     if(typeof callback === "function")
4680                         callback();
4681                 });
4682             }
4683         }
4684         else {
4685             NETDATA.chartLibraries.morris.enabled = false;
4686             if(typeof callback === "function")
4687                 callback();
4688         }
4689     };
4690
4691     NETDATA.morrisChartUpdate = function(state, data) {
4692         state.morris_instance.setData(data.result.data);
4693         return true;
4694     };
4695
4696     NETDATA.morrisChartCreate = function(state, data) {
4697
4698         state.morris_options = {
4699                 element: state.element_chart.id,
4700                 data: data.result.data,
4701                 xkey: 'time',
4702                 ykeys: data.dimension_names,
4703                 labels: data.dimension_names,
4704                 lineWidth: 2,
4705                 pointSize: 3,
4706                 smooth: true,
4707                 hideHover: 'auto',
4708                 parseTime: true,
4709                 continuousLine: false,
4710                 behaveLikeLine: false
4711         };
4712
4713         if(state.chart.chart_type === 'line')
4714             state.morris_instance = new Morris.Line(state.morris_options);
4715
4716         else if(state.chart.chart_type === 'area') {
4717             state.morris_options.behaveLikeLine = true;
4718             state.morris_instance = new Morris.Area(state.morris_options);
4719         }
4720         else // stacked
4721             state.morris_instance = new Morris.Area(state.morris_options);
4722
4723         return true;
4724     };
4725
4726     // ----------------------------------------------------------------------------------------------------------------
4727     // raphael
4728
4729     NETDATA.raphaelInitialize = function(callback) {
4730         if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4731             $.ajax({
4732                 url: NETDATA.raphael_js,
4733                 cache: true,
4734                 dataType: "script",
4735                 xhrFields: { withCredentials: true } // required for the cookie
4736             })
4737             .done(function() {
4738                 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4739             })
4740             .fail(function() {
4741                 NETDATA.chartLibraries.raphael.enabled = false;
4742                 NETDATA.error(100, NETDATA.raphael_js);
4743             })
4744             .always(function() {
4745                 if(typeof callback === "function")
4746                     callback();
4747             });
4748         }
4749         else {
4750             NETDATA.chartLibraries.raphael.enabled = false;
4751             if(typeof callback === "function")
4752                 callback();
4753         }
4754     };
4755
4756     NETDATA.raphaelChartUpdate = function(state, data) {
4757         $(state.element_chart).raphael(data.result, {
4758             width: state.chartWidth(),
4759             height: state.chartHeight()
4760         });
4761
4762         return false;
4763     };
4764
4765     NETDATA.raphaelChartCreate = function(state, data) {
4766         $(state.element_chart).raphael(data.result, {
4767             width: state.chartWidth(),
4768             height: state.chartHeight()
4769         });
4770
4771         return false;
4772     };
4773
4774     // ----------------------------------------------------------------------------------------------------------------
4775     // C3
4776
4777     NETDATA.c3Initialize = function(callback) {
4778         if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4779
4780             // C3 requires D3
4781             if(!NETDATA.chartLibraries.d3.initialized) {
4782                 if(NETDATA.chartLibraries.d3.enabled) {
4783                     NETDATA.d3Initialize(function() {
4784                         NETDATA.c3Initialize(callback);
4785                     });
4786                 }
4787                 else {
4788                     NETDATA.chartLibraries.c3.enabled = false;
4789                     if(typeof callback === "function")
4790                         callback();
4791                 }
4792             }
4793             else {
4794                 NETDATA._loadCSS(NETDATA.c3_css);
4795
4796                 $.ajax({
4797                     url: NETDATA.c3_js,
4798                     cache: true,
4799                     dataType: "script",
4800                     xhrFields: { withCredentials: true } // required for the cookie
4801                 })
4802                 .done(function() {
4803                     NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4804                 })
4805                 .fail(function() {
4806                     NETDATA.chartLibraries.c3.enabled = false;
4807                     NETDATA.error(100, NETDATA.c3_js);
4808                 })
4809                 .always(function() {
4810                     if(typeof callback === "function")
4811                         callback();
4812                 });
4813             }
4814         }
4815         else {
4816             NETDATA.chartLibraries.c3.enabled = false;
4817             if(typeof callback === "function")
4818                 callback();
4819         }
4820     };
4821
4822     NETDATA.c3ChartUpdate = function(state, data) {
4823         state.c3_instance.destroy();
4824         return NETDATA.c3ChartCreate(state, data);
4825
4826         //state.c3_instance.load({
4827         //  rows: data.result,
4828         //  unload: true
4829         //});
4830
4831         //return true;
4832     };
4833
4834     NETDATA.c3ChartCreate = function(state, data) {
4835
4836         state.element_chart.id = 'c3-' + state.uuid;
4837         // console.log('id = ' + state.element_chart.id);
4838
4839         state.c3_instance = c3.generate({
4840             bindto: '#' + state.element_chart.id,
4841             size: {
4842                 width: state.chartWidth(),
4843                 height: state.chartHeight()
4844             },
4845             color: {
4846                 pattern: state.chartColors()
4847             },
4848             data: {
4849                 x: 'time',
4850                 rows: data.result,
4851                 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4852             },
4853             axis: {
4854                 x: {
4855                     type: 'timeseries',
4856                     tick: {
4857                         format: function(x) {
4858                             return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4859                         }
4860                     }
4861                 }
4862             },
4863             grid: {
4864                 x: {
4865                     show: true
4866                 },
4867                 y: {
4868                     show: true
4869                 }
4870             },
4871             point: {
4872                 show: false
4873             },
4874             line: {
4875                 connectNull: false
4876             },
4877             transition: {
4878                 duration: 0
4879             },
4880             interaction: {
4881                 enabled: true
4882             }
4883         });
4884
4885         // console.log(state.c3_instance);
4886
4887         return true;
4888     };
4889
4890     // ----------------------------------------------------------------------------------------------------------------
4891     // D3
4892
4893     NETDATA.d3Initialize = function(callback) {
4894         if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4895             $.ajax({
4896                 url: NETDATA.d3_js,
4897                 cache: true,
4898                 dataType: "script",
4899                 xhrFields: { withCredentials: true } // required for the cookie
4900             })
4901             .done(function() {
4902                 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4903             })
4904             .fail(function() {
4905                 NETDATA.chartLibraries.d3.enabled = false;
4906                 NETDATA.error(100, NETDATA.d3_js);
4907             })
4908             .always(function() {
4909                 if(typeof callback === "function")
4910                     callback();
4911             });
4912         }
4913         else {
4914             NETDATA.chartLibraries.d3.enabled = false;
4915             if(typeof callback === "function")
4916                 callback();
4917         }
4918     };
4919
4920     NETDATA.d3ChartUpdate = function(state, data) {
4921         return false;
4922     };
4923
4924     NETDATA.d3ChartCreate = function(state, data) {
4925         return false;
4926     };
4927
4928     // ----------------------------------------------------------------------------------------------------------------
4929     // google charts
4930
4931     NETDATA.googleInitialize = function(callback) {
4932         if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4933             $.ajax({
4934                 url: NETDATA.google_js,
4935                 cache: true,
4936                 dataType: "script",
4937                 xhrFields: { withCredentials: true } // required for the cookie
4938             })
4939             .done(function() {
4940                 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4941                 google.load('visualization', '1.1', {
4942                     'packages': ['corechart', 'controls'],
4943                     'callback': callback
4944                 });
4945             })
4946             .fail(function() {
4947                 NETDATA.chartLibraries.google.enabled = false;
4948                 NETDATA.error(100, NETDATA.google_js);
4949                 if(typeof callback === "function")
4950                     callback();
4951             });
4952         }
4953         else {
4954             NETDATA.chartLibraries.google.enabled = false;
4955             if(typeof callback === "function")
4956                 callback();
4957         }
4958     };
4959
4960     NETDATA.googleChartUpdate = function(state, data) {
4961         var datatable = new google.visualization.DataTable(data.result);
4962         state.google_instance.draw(datatable, state.google_options);
4963         return true;
4964     };
4965
4966     NETDATA.googleChartCreate = function(state, data) {
4967         var datatable = new google.visualization.DataTable(data.result);
4968
4969         state.google_options = {
4970             colors: state.chartColors(),
4971
4972             // do not set width, height - the chart resizes itself
4973             //width: state.chartWidth(),
4974             //height: state.chartHeight(),
4975             lineWidth: 1,
4976             title: state.title,
4977             fontSize: 11,
4978             hAxis: {
4979             //  title: "Time of Day",
4980             //  format:'HH:mm:ss',
4981                 viewWindowMode: 'maximized',
4982                 slantedText: false,
4983                 format:'HH:mm:ss',
4984                 textStyle: {
4985                     fontSize: 9
4986                 },
4987                 gridlines: {
4988                     color: '#EEE'
4989                 }
4990             },
4991             vAxis: {
4992                 title: state.units,
4993                 viewWindowMode: 'pretty',
4994                 minValue: -0.1,
4995                 maxValue: 0.1,
4996                 direction: 1,
4997                 textStyle: {
4998                     fontSize: 9
4999                 },
5000                 gridlines: {
5001                     color: '#EEE'
5002                 }
5003             },
5004             chartArea: {
5005                 width: '65%',
5006                 height: '80%'
5007             },
5008             focusTarget: 'category',
5009             annotation: {
5010                 '1': {
5011                     style: 'line'
5012                 }
5013             },
5014             pointsVisible: 0,
5015             titlePosition: 'out',
5016             titleTextStyle: {
5017                 fontSize: 11
5018             },
5019             tooltip: {
5020                 isHtml: false,
5021                 ignoreBounds: true,
5022                 textStyle: {
5023                     fontSize: 9
5024                 }
5025             },
5026             curveType: 'function',
5027             areaOpacity: 0.3,
5028             isStacked: false
5029         };
5030
5031         switch(state.chart.chart_type) {
5032             case "area":
5033                 state.google_options.vAxis.viewWindowMode = 'maximized';
5034                 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
5035                 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5036                 break;
5037
5038             case "stacked":
5039                 state.google_options.isStacked = true;
5040                 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5041                 state.google_options.vAxis.viewWindowMode = 'maximized';
5042                 state.google_options.vAxis.minValue = null;
5043                 state.google_options.vAxis.maxValue = null;
5044                 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5045                 break;
5046
5047             default:
5048             case "line":
5049                 state.google_options.lineWidth = 2;
5050                 state.google_instance = new google.visualization.LineChart(state.element_chart);
5051                 break;
5052         }
5053
5054         state.google_instance.draw(datatable, state.google_options);
5055         return true;
5056     };
5057
5058     // ----------------------------------------------------------------------------------------------------------------
5059
5060     NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5061         if(typeof value !== 'number') value = 0;
5062         if(typeof min !== 'number') min = 0;
5063         if(typeof max !== 'number') max = 0;
5064
5065         if(min > value) min = value;
5066         if(max < value) max = value;
5067
5068         // make sure it is zero based
5069         if(min > 0) min = 0;
5070         if(max < 0) max = 0;
5071
5072         var pcent = 0;
5073         if(value >= 0) {
5074             if(max !== 0)
5075                 pcent = Math.round(value * 100 / max);
5076             if(pcent === 0) pcent = 0.1;
5077         }
5078         else {
5079             if(min !== 0)
5080                 pcent = Math.round(-value * 100 / min);
5081             if(pcent === 0) pcent = -0.1;
5082         }
5083
5084         return pcent;
5085     };
5086
5087     // ----------------------------------------------------------------------------------------------------------------
5088     // easy-pie-chart
5089
5090     NETDATA.easypiechartInitialize = function(callback) {
5091         if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5092             $.ajax({
5093                 url: NETDATA.easypiechart_js,
5094                 cache: true,
5095                 dataType: "script",
5096                 xhrFields: { withCredentials: true } // required for the cookie
5097             })
5098                 .done(function() {
5099                     NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5100                 })
5101                 .fail(function() {
5102                     NETDATA.chartLibraries.easypiechart.enabled = false;
5103                     NETDATA.error(100, NETDATA.easypiechart_js);
5104                 })
5105                 .always(function() {
5106                     if(typeof callback === "function")
5107                         callback();
5108                 })
5109         }
5110         else {
5111             NETDATA.chartLibraries.easypiechart.enabled = false;
5112             if(typeof callback === "function")
5113                 callback();
5114         }
5115     };
5116
5117     NETDATA.easypiechartClearSelection = function(state) {
5118         if(typeof state.easyPieChartEvent !== 'undefined') {
5119             if(state.easyPieChartEvent.timer !== null)
5120                 clearTimeout(state.easyPieChartEvent.timer);
5121
5122             state.easyPieChartEvent.timer = null;
5123         }
5124
5125         if(state.isAutoRefreshable() === true && state.data !== null) {
5126             NETDATA.easypiechartChartUpdate(state, state.data);
5127         }
5128         else {
5129             state.easyPieChartLabel.innerText = state.legendFormatValue(null);
5130             state.easyPieChart_instance.update(0);
5131         }
5132         state.easyPieChart_instance.enableAnimation();
5133
5134         return true;
5135     };
5136
5137     NETDATA.easypiechartSetSelection = function(state, t) {
5138         if(state.timeIsVisible(t) !== true)
5139             return NETDATA.easypiechartClearSelection(state);
5140
5141         var slot = state.calculateRowForTime(t);
5142         if(slot < 0 || slot >= state.data.result.length)
5143             return NETDATA.easypiechartClearSelection(state);
5144
5145         if(typeof state.easyPieChartEvent === 'undefined') {
5146             state.easyPieChartEvent = {
5147                 timer: null,
5148                 value: 0,
5149                 pcent: 0
5150             };
5151         }
5152
5153         var value = state.data.result[state.data.result.length - 1 - slot];
5154         var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5155         var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5156         var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5157
5158         state.easyPieChartEvent.value = value;
5159         state.easyPieChartEvent.pcent = pcent;
5160         state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5161
5162         if(state.easyPieChartEvent.timer === null) {
5163             state.easyPieChart_instance.disableAnimation();
5164
5165             state.easyPieChartEvent.timer = setTimeout(function() {
5166                 state.easyPieChartEvent.timer = null;
5167                 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5168             }, NETDATA.options.current.charts_selection_animation_delay);
5169         }
5170
5171         return true;
5172     };
5173
5174     NETDATA.easypiechartChartUpdate = function(state, data) {
5175         var value, min, max, pcent;
5176
5177         if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5178             value = null;
5179             pcent = 0;
5180         }
5181         else {
5182             value = data.result[0];
5183             min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5184             max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5185             pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5186         }
5187
5188         state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5189         state.easyPieChart_instance.update(pcent);
5190         return true;
5191     };
5192
5193     NETDATA.easypiechartChartCreate = function(state, data) {
5194         var self = $(state.element);
5195         var chart = $(state.element_chart);
5196
5197         var value = data.result[0];
5198         var min = self.data('easypiechart-min-value') || null;
5199         var max = self.data('easypiechart-max-value') || null;
5200         var adjust = self.data('easypiechart-adjust') || null;
5201
5202         if(min === null) {
5203             min = NETDATA.commonMin.get(state);
5204             state.easyPieChartMin = null;
5205         }
5206         else
5207             state.easyPieChartMin = min;
5208
5209         if(max === null) {
5210             max = NETDATA.commonMax.get(state);
5211             state.easyPieChartMax = null;
5212         }
5213         else
5214             state.easyPieChartMax = max;
5215
5216         var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5217
5218         chart.data('data-percent', pcent);
5219
5220         var size;
5221         switch(adjust) {
5222             case 'width': size = state.chartHeight(); break;
5223             case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5224             case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5225             case 'height':
5226             default: size = state.chartWidth(); break;
5227         }
5228         state.element.style.width = size + 'px';
5229         state.element.style.height = size + 'px';
5230
5231         var stroke = Math.floor(size / 22);
5232         if(stroke < 3) stroke = 2;
5233
5234         var valuefontsize = Math.floor((size * 2 / 3) / 5);
5235         var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5236         state.easyPieChartLabel = document.createElement('span');
5237         state.easyPieChartLabel.className = 'easyPieChartLabel';
5238         state.easyPieChartLabel.innerText = state.legendFormatValue(value);
5239         state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5240         state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5241         state.element_chart.appendChild(state.easyPieChartLabel);
5242
5243         var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5244         var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5245         state.easyPieChartTitle = document.createElement('span');
5246         state.easyPieChartTitle.className = 'easyPieChartTitle';
5247         state.easyPieChartTitle.innerText = state.title;
5248         state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5249         state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5250         state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5251         state.element_chart.appendChild(state.easyPieChartTitle);
5252
5253         var unitfontsize = Math.round(titlefontsize * 0.9);
5254         var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5255         state.easyPieChartUnits = document.createElement('span');
5256         state.easyPieChartUnits.className = 'easyPieChartUnits';
5257         state.easyPieChartUnits.innerText = state.units;
5258         state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5259         state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5260         state.element_chart.appendChild(state.easyPieChartUnits);
5261
5262         var barColor = self.data('easypiechart-barcolor');
5263         if(typeof barColor === 'undefined' || barColor === null)
5264             barColor = state.chartColors()[0];
5265         else {
5266             // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5267             var tmp = eval(barColor);
5268             if(typeof tmp === 'function')
5269                 barColor = tmp;
5270         }
5271
5272         chart.easyPieChart({
5273             barColor: barColor,
5274             trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5275             scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5276             scaleLength: self.data('easypiechart-scalelength') || 5,
5277             lineCap: self.data('easypiechart-linecap') || 'round',
5278             lineWidth: self.data('easypiechart-linewidth') || stroke,
5279             trackWidth: self.data('easypiechart-trackwidth') || undefined,
5280             size: self.data('easypiechart-size') || size,
5281             rotate: self.data('easypiechart-rotate') || 0,
5282             animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5283             easing: self.data('easypiechart-easing') || undefined
5284         });
5285
5286         // when we just re-create the chart
5287         // do not animate the first update
5288         var animate = true;
5289         if(typeof state.easyPieChart_instance !== 'undefined')
5290             animate = false;
5291
5292         state.easyPieChart_instance = chart.data('easyPieChart');
5293         if(animate === false) state.easyPieChart_instance.disableAnimation();
5294         state.easyPieChart_instance.update(pcent);
5295         if(animate === false) state.easyPieChart_instance.enableAnimation();
5296         return true;
5297     };
5298
5299     // ----------------------------------------------------------------------------------------------------------------
5300     // gauge.js
5301
5302     NETDATA.gaugeInitialize = function(callback) {
5303         if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5304             $.ajax({
5305                 url: NETDATA.gauge_js,
5306                 cache: true,
5307                 dataType: "script",
5308                 xhrFields: { withCredentials: true } // required for the cookie
5309             })
5310                 .done(function() {
5311                     NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5312                 })
5313                 .fail(function() {
5314                     NETDATA.chartLibraries.gauge.enabled = false;
5315                     NETDATA.error(100, NETDATA.gauge_js);
5316                 })
5317                 .always(function() {
5318                     if(typeof callback === "function")
5319                         callback();
5320                 })
5321         }
5322         else {
5323             NETDATA.chartLibraries.gauge.enabled = false;
5324             if(typeof callback === "function")
5325                 callback();
5326         }
5327     };
5328
5329     NETDATA.gaugeAnimation = function(state, status) {
5330         var speed = 32;
5331
5332         if(typeof status === 'boolean' && status === false)
5333             speed = 1000000000;
5334         else if(typeof status === 'number')
5335             speed = status;
5336
5337         // console.log('gauge speed ' + speed);
5338         state.gauge_instance.animationSpeed = speed;
5339         state.___gaugeOld__.speed = speed;
5340     };
5341
5342     NETDATA.gaugeSet = function(state, value, min, max) {
5343         if(typeof value !== 'number') value = 0;
5344         if(typeof min !== 'number') min = 0;
5345         if(typeof max !== 'number') max = 0;
5346         if(value > max) max = value;
5347         if(value < min) min = value;
5348         if(min > max) {
5349             var t = min;
5350             min = max;
5351             max = t;
5352         }
5353         else if(min == max)
5354             max = min + 1;
5355
5356         // gauge.js has an issue if the needle
5357         // is smaller than min or larger than max
5358         // when we set the new values
5359         // the needle will go crazy
5360
5361         // to prevent it, we always feed it
5362         // with a percentage, so that the needle
5363         // is always between min and max
5364         var pcent = (value - min) * 100 / (max - min);
5365
5366         // these should never happen
5367         if(pcent < 0) pcent = 0;
5368         if(pcent > 100) pcent = 100;
5369
5370         state.gauge_instance.set(pcent);
5371         // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5372
5373         state.___gaugeOld__.value = value;
5374         state.___gaugeOld__.min = min;
5375         state.___gaugeOld__.max = max;
5376     };
5377
5378     NETDATA.gaugeSetLabels = function(state, value, min, max) {
5379         if(state.___gaugeOld__.valueLabel !== value) {
5380             state.___gaugeOld__.valueLabel = value;
5381             state.gaugeChartLabel.innerText = state.legendFormatValue(value);
5382         }
5383         if(state.___gaugeOld__.minLabel !== min) {
5384             state.___gaugeOld__.minLabel = min;
5385             state.gaugeChartMin.innerText = state.legendFormatValue(min);
5386         }
5387         if(state.___gaugeOld__.maxLabel !== max) {
5388             state.___gaugeOld__.maxLabel = max;
5389             state.gaugeChartMax.innerText = state.legendFormatValue(max);
5390         }
5391     };
5392
5393     NETDATA.gaugeClearSelection = function(state) {
5394         if(typeof state.gaugeEvent !== 'undefined') {
5395             if(state.gaugeEvent.timer !== null)
5396                 clearTimeout(state.gaugeEvent.timer);
5397
5398             state.gaugeEvent.timer = null;
5399         }
5400
5401         if(state.isAutoRefreshable() === true && state.data !== null) {
5402             NETDATA.gaugeChartUpdate(state, state.data);
5403         }
5404         else {
5405             NETDATA.gaugeAnimation(state, false);
5406             NETDATA.gaugeSet(state, null, null, null);
5407             NETDATA.gaugeSetLabels(state, null, null, null);
5408         }
5409
5410         NETDATA.gaugeAnimation(state, true);
5411         return true;
5412     };
5413
5414     NETDATA.gaugeSetSelection = function(state, t) {
5415         if(state.timeIsVisible(t) !== true)
5416             return NETDATA.gaugeClearSelection(state);
5417
5418         var slot = state.calculateRowForTime(t);
5419         if(slot < 0 || slot >= state.data.result.length)
5420             return NETDATA.gaugeClearSelection(state);
5421
5422         if(typeof state.gaugeEvent === 'undefined') {
5423             state.gaugeEvent = {
5424                 timer: null,
5425                 value: 0,
5426                 min: 0,
5427                 max: 0
5428             };
5429         }
5430
5431         var value = state.data.result[state.data.result.length - 1 - slot];
5432         var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5433         var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5434
5435         // make sure it is zero based
5436         if(min > 0) min = 0;
5437         if(max < 0) max = 0;
5438
5439         state.gaugeEvent.value = value;
5440         state.gaugeEvent.min = min;
5441         state.gaugeEvent.max = max;
5442         NETDATA.gaugeSetLabels(state, value, min, max);
5443
5444         if(state.gaugeEvent.timer === null) {
5445             NETDATA.gaugeAnimation(state, false);
5446
5447             state.gaugeEvent.timer = setTimeout(function() {
5448                 state.gaugeEvent.timer = null;
5449                 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5450             }, NETDATA.options.current.charts_selection_animation_delay);
5451         }
5452
5453         return true;
5454     };
5455
5456     NETDATA.gaugeChartUpdate = function(state, data) {
5457         var value, min, max;
5458
5459         if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5460             value = 0;
5461             min = 0;
5462             max = 1;
5463             NETDATA.gaugeSetLabels(state, null, null, null);
5464         }
5465         else {
5466             value = data.result[0];
5467             min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5468             max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5469             if(value < min) min = value;
5470             if(value > max) max = value;
5471
5472             // make sure it is zero based
5473             if(min > 0) min = 0;
5474             if(max < 0) max = 0;
5475
5476             NETDATA.gaugeSetLabels(state, value, min, max);
5477         }
5478
5479         NETDATA.gaugeSet(state, value, min, max);
5480         return true;
5481     };
5482
5483     NETDATA.gaugeChartCreate = function(state, data) {
5484         var self = $(state.element);
5485         // var chart = $(state.element_chart);
5486
5487         var value = data.result[0];
5488         var min = self.data('gauge-min-value') || null;
5489         var max = self.data('gauge-max-value') || null;
5490         var adjust = self.data('gauge-adjust') || null;
5491         var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5492         var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5493         var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5494         var stopColor = self.data('gauge-stop-color') || void 0;
5495         var generateGradient = self.data('gauge-generate-gradient') || false;
5496
5497         if(min === null) {
5498             min = NETDATA.commonMin.get(state);
5499             state.gaugeMin = null;
5500         }
5501         else
5502             state.gaugeMin = min;
5503
5504         if(max === null) {
5505             max = NETDATA.commonMax.get(state);
5506             state.gaugeMax = null;
5507         }
5508         else
5509             state.gaugeMax = max;
5510
5511         // make sure it is zero based
5512         if(min > 0) min = 0;
5513         if(max < 0) max = 0;
5514
5515         var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5516         //switch(adjust) {
5517         //  case 'width': width = height * ratio; break;
5518         //  case 'height':
5519         //  default: height = width / ratio; break;
5520         //}
5521         //state.element.style.width = width.toString() + 'px';
5522         //state.element.style.height = height.toString() + 'px';
5523
5524         var lum_d = 0.05;
5525
5526         var options = {
5527             lines: 12,                  // The number of lines to draw
5528             angle: 0.15,                // The length of each line
5529             lineWidth: 0.44,            // 0.44 The line thickness
5530             pointer: {
5531                 length: 0.8,            // 0.9 The radius of the inner circle
5532                 strokeWidth: 0.035,     // The rotation offset
5533                 color: pointerColor     // Fill color
5534             },
5535             colorStart: startColor,     // Colors
5536             colorStop: stopColor,       // just experiment with them
5537             strokeColor: strokeColor,   // to see which ones work best for you
5538             limitMax: true,
5539             generateGradient: (generateGradient === true)?true:false,
5540             gradientType: 0
5541         };
5542
5543         if (generateGradient.constructor === Array) {
5544             // example options:
5545             // data-gauge-generate-gradient="[0, 50, 100]"
5546             // data-gauge-gradient-percent-color-0="#FFFFFF"
5547             // data-gauge-gradient-percent-color-50="#999900"
5548             // data-gauge-gradient-percent-color-100="#000000"
5549
5550             options.percentColors = new Array();
5551             var len = generateGradient.length;
5552             while(len--) {
5553                 var pcent = generateGradient[len];
5554                 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5555                 if(color !== false) {
5556                     var a = new Array();
5557                     a[0] = pcent / 100;
5558                     a[1] = color;
5559                     options.percentColors.unshift(a);
5560                 }
5561             }
5562             if(options.percentColors.length === 0)
5563                 delete options.percentColors;
5564         }
5565         else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5566             options.percentColors = [
5567                 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5568                 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5569                 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5570                 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5571                 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5572                 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5573                 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5574                 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5575                 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5576                 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5577                 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5578         }
5579
5580         state.gauge_canvas = document.createElement('canvas');
5581         state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5582         state.gauge_canvas.className = 'gaugeChart';
5583         state.gauge_canvas.width  = width;
5584         state.gauge_canvas.height = height;
5585         state.element_chart.appendChild(state.gauge_canvas);
5586
5587         var valuefontsize = Math.floor(height / 6);
5588         var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5589         state.gaugeChartLabel = document.createElement('span');
5590         state.gaugeChartLabel.className = 'gaugeChartLabel';
5591         state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5592         state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5593         state.element_chart.appendChild(state.gaugeChartLabel);
5594
5595         var titlefontsize = Math.round(valuefontsize / 2);
5596         var titletop = 0;
5597         state.gaugeChartTitle = document.createElement('span');
5598         state.gaugeChartTitle.className = 'gaugeChartTitle';
5599         state.gaugeChartTitle.innerText = state.title;
5600         state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5601         state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5602         state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5603         state.element_chart.appendChild(state.gaugeChartTitle);
5604
5605         var unitfontsize = Math.round(titlefontsize * 0.9);
5606         state.gaugeChartUnits = document.createElement('span');
5607         state.gaugeChartUnits.className = 'gaugeChartUnits';
5608         state.gaugeChartUnits.innerText = state.units;
5609         state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5610         state.element_chart.appendChild(state.gaugeChartUnits);
5611
5612         state.gaugeChartMin = document.createElement('span');
5613         state.gaugeChartMin.className = 'gaugeChartMin';
5614         state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5615         state.element_chart.appendChild(state.gaugeChartMin);
5616
5617         state.gaugeChartMax = document.createElement('span');
5618         state.gaugeChartMax.className = 'gaugeChartMax';
5619         state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5620         state.element_chart.appendChild(state.gaugeChartMax);
5621
5622         // when we just re-create the chart
5623         // do not animate the first update
5624         var animate = true;
5625         if(typeof state.gauge_instance !== 'undefined')
5626             animate = false;
5627
5628         state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5629
5630         state.___gaugeOld__ = {
5631             value: value,
5632             min: min,
5633             max: max,
5634             valueLabel: null,
5635             minLabel: null,
5636             maxLabel: null
5637         };
5638
5639         // we will always feed a percentage
5640         state.gauge_instance.minValue = 0;
5641         state.gauge_instance.maxValue = 100;
5642
5643         NETDATA.gaugeAnimation(state, animate);
5644         NETDATA.gaugeSet(state, value, min, max);
5645         NETDATA.gaugeSetLabels(state, value, min, max);
5646         NETDATA.gaugeAnimation(state, true);
5647         return true;
5648     };
5649
5650     // ----------------------------------------------------------------------------------------------------------------
5651     // Charts Libraries Registration
5652
5653     NETDATA.chartLibraries = {
5654         "dygraph": {
5655             initialize: NETDATA.dygraphInitialize,
5656             create: NETDATA.dygraphChartCreate,
5657             update: NETDATA.dygraphChartUpdate,
5658             resize: function(state) {
5659                 if(typeof state.dygraph_instance.resize === 'function')
5660                     state.dygraph_instance.resize();
5661             },
5662             setSelection: NETDATA.dygraphSetSelection,
5663             clearSelection:  NETDATA.dygraphClearSelection,
5664             toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5665             initialized: false,
5666             enabled: true,
5667             format: function(state) { return 'json'; },
5668             options: function(state) { return 'ms|flip'; },
5669             legend: function(state) {
5670                 if(this.isSparkline(state) === false)
5671                     return 'right-side';
5672                 else
5673                     return null;
5674             },
5675             autoresize: function(state) { return true; },
5676             max_updates_to_recreate: function(state) { return 5000; },
5677             track_colors: function(state) { return true; },
5678             pixels_per_point: function(state) {
5679                 if(this.isSparkline(state) === false)
5680                     return 3;
5681                 else
5682                     return 2;
5683             },
5684
5685             isSparkline: function(state) {
5686                 if(typeof state.dygraph_sparkline === 'undefined') {
5687                     var t = $(state.element).data('dygraph-theme');
5688                     if(t === 'sparkline')
5689                         state.dygraph_sparkline = true;
5690                     else
5691                         state.dygraph_sparkline = false;
5692                 }
5693                 return state.dygraph_sparkline;
5694             }
5695         },
5696         "sparkline": {
5697             initialize: NETDATA.sparklineInitialize,
5698             create: NETDATA.sparklineChartCreate,
5699             update: NETDATA.sparklineChartUpdate,
5700             resize: null,
5701             setSelection: undefined, // function(state, t) { return true; },
5702             clearSelection: undefined, // function(state) { return true; },
5703             toolboxPanAndZoom: null,
5704             initialized: false,
5705             enabled: true,
5706             format: function(state) { return 'array'; },
5707             options: function(state) { return 'flip|abs'; },
5708             legend: function(state) { return null; },
5709             autoresize: function(state) { return false; },
5710             max_updates_to_recreate: function(state) { return 5000; },
5711             track_colors: function(state) { return false; },
5712             pixels_per_point: function(state) { return 3; }
5713         },
5714         "peity": {
5715             initialize: NETDATA.peityInitialize,
5716             create: NETDATA.peityChartCreate,
5717             update: NETDATA.peityChartUpdate,
5718             resize: null,
5719             setSelection: undefined, // function(state, t) { return true; },
5720             clearSelection: undefined, // function(state) { return true; },
5721             toolboxPanAndZoom: null,
5722             initialized: false,
5723             enabled: true,
5724             format: function(state) { return 'ssvcomma'; },
5725             options: function(state) { return 'null2zero|flip|abs'; },
5726             legend: function(state) { return null; },
5727             autoresize: function(state) { return false; },
5728             max_updates_to_recreate: function(state) { return 5000; },
5729             track_colors: function(state) { return false; },
5730             pixels_per_point: function(state) { return 3; }
5731         },
5732         "morris": {
5733             initialize: NETDATA.morrisInitialize,
5734             create: NETDATA.morrisChartCreate,
5735             update: NETDATA.morrisChartUpdate,
5736             resize: null,
5737             setSelection: undefined, // function(state, t) { return true; },
5738             clearSelection: undefined, // function(state) { return true; },
5739             toolboxPanAndZoom: null,
5740             initialized: false,
5741             enabled: true,
5742             format: function(state) { return 'json'; },
5743             options: function(state) { return 'objectrows|ms'; },
5744             legend: function(state) { return null; },
5745             autoresize: function(state) { return false; },
5746             max_updates_to_recreate: function(state) { return 50; },
5747             track_colors: function(state) { return false; },
5748             pixels_per_point: function(state) { return 15; }
5749         },
5750         "google": {
5751             initialize: NETDATA.googleInitialize,
5752             create: NETDATA.googleChartCreate,
5753             update: NETDATA.googleChartUpdate,
5754             resize: null,
5755             setSelection: undefined, //function(state, t) { return true; },
5756             clearSelection: undefined, //function(state) { return true; },
5757             toolboxPanAndZoom: null,
5758             initialized: false,
5759             enabled: true,
5760             format: function(state) { return 'datatable'; },
5761             options: function(state) { return ''; },
5762             legend: function(state) { return null; },
5763             autoresize: function(state) { return false; },
5764             max_updates_to_recreate: function(state) { return 300; },
5765             track_colors: function(state) { return false; },
5766             pixels_per_point: function(state) { return 4; }
5767         },
5768         "raphael": {
5769             initialize: NETDATA.raphaelInitialize,
5770             create: NETDATA.raphaelChartCreate,
5771             update: NETDATA.raphaelChartUpdate,
5772             resize: null,
5773             setSelection: undefined, // function(state, t) { return true; },
5774             clearSelection: undefined, // function(state) { return true; },
5775             toolboxPanAndZoom: null,
5776             initialized: false,
5777             enabled: true,
5778             format: function(state) { return 'json'; },
5779             options: function(state) { return ''; },
5780             legend: function(state) { return null; },
5781             autoresize: function(state) { return false; },
5782             max_updates_to_recreate: function(state) { return 5000; },
5783             track_colors: function(state) { return false; },
5784             pixels_per_point: function(state) { return 3; }
5785         },
5786         "c3": {
5787             initialize: NETDATA.c3Initialize,
5788             create: NETDATA.c3ChartCreate,
5789             update: NETDATA.c3ChartUpdate,
5790             resize: null,
5791             setSelection: undefined, // function(state, t) { return true; },
5792             clearSelection: undefined, // function(state) { return true; },
5793             toolboxPanAndZoom: null,
5794             initialized: false,
5795             enabled: true,
5796             format: function(state) { return 'csvjsonarray'; },
5797             options: function(state) { return 'milliseconds'; },
5798             legend: function(state) { return null; },
5799             autoresize: function(state) { return false; },
5800             max_updates_to_recreate: function(state) { return 5000; },
5801             track_colors: function(state) { return false; },
5802             pixels_per_point: function(state) { return 15; }
5803         },
5804         "d3": {
5805             initialize: NETDATA.d3Initialize,
5806             create: NETDATA.d3ChartCreate,
5807             update: NETDATA.d3ChartUpdate,
5808             resize: null,
5809             setSelection: undefined, // function(state, t) { return true; },
5810             clearSelection: undefined, // function(state) { return true; },
5811             toolboxPanAndZoom: null,
5812             initialized: false,
5813             enabled: true,
5814             format: function(state) { return 'json'; },
5815             options: function(state) { return ''; },
5816             legend: function(state) { return null; },
5817             autoresize: function(state) { return false; },
5818             max_updates_to_recreate: function(state) { return 5000; },
5819             track_colors: function(state) { return false; },
5820             pixels_per_point: function(state) { return 3; }
5821         },
5822         "easypiechart": {
5823             initialize: NETDATA.easypiechartInitialize,
5824             create: NETDATA.easypiechartChartCreate,
5825             update: NETDATA.easypiechartChartUpdate,
5826             resize: null,
5827             setSelection: NETDATA.easypiechartSetSelection,
5828             clearSelection: NETDATA.easypiechartClearSelection,
5829             toolboxPanAndZoom: null,
5830             initialized: false,
5831             enabled: true,
5832             format: function(state) { return 'array'; },
5833             options: function(state) { return 'absolute'; },
5834             legend: function(state) { return null; },
5835             autoresize: function(state) { return false; },
5836             max_updates_to_recreate: function(state) { return 5000; },
5837             track_colors: function(state) { return true; },
5838             pixels_per_point: function(state) { return 3; },
5839             aspect_ratio: 100
5840         },
5841         "gauge": {
5842             initialize: NETDATA.gaugeInitialize,
5843             create: NETDATA.gaugeChartCreate,
5844             update: NETDATA.gaugeChartUpdate,
5845             resize: null,
5846             setSelection: NETDATA.gaugeSetSelection,
5847             clearSelection: NETDATA.gaugeClearSelection,
5848             toolboxPanAndZoom: null,
5849             initialized: false,
5850             enabled: true,
5851             format: function(state) { return 'array'; },
5852             options: function(state) { return 'absolute'; },
5853             legend: function(state) { return null; },
5854             autoresize: function(state) { return false; },
5855             max_updates_to_recreate: function(state) { return 5000; },
5856             track_colors: function(state) { return true; },
5857             pixels_per_point: function(state) { return 3; },
5858             aspect_ratio: 70
5859         }
5860     };
5861
5862     NETDATA.registerChartLibrary = function(library, url) {
5863         if(NETDATA.options.debug.libraries === true)
5864             console.log("registering chart library: " + library);
5865
5866         NETDATA.chartLibraries[library].url = url;
5867         NETDATA.chartLibraries[library].initialized = true;
5868         NETDATA.chartLibraries[library].enabled = true;
5869     };
5870
5871     // ----------------------------------------------------------------------------------------------------------------
5872     // Load required JS libraries and CSS
5873
5874     NETDATA.requiredJs = [
5875         {
5876             url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5877             async: false,
5878             isAlreadyLoaded: function() {
5879                 // check if bootstrap is loaded
5880                 if(typeof $().emulateTransitionEnd == 'function')
5881                     return true;
5882                 else {
5883                     if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5884                         return true;
5885                     else
5886                         return false;
5887                 }
5888             }
5889         },
5890         {
5891             url: NETDATA.serverDefault + 'lib/jquery.nanoscroller-0.8.7.min.js',
5892             isAlreadyLoaded: function() { return false; }
5893         }
5894     ];
5895
5896     NETDATA.requiredCSS = [
5897         {
5898             url: NETDATA.themes.current.bootstrap_css,
5899             isAlreadyLoaded: function() {
5900                 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5901                     return true;
5902                 else
5903                     return false;
5904             }
5905         },
5906         {
5907             url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
5908             isAlreadyLoaded: function() { return false; }
5909         },
5910         {
5911             url: NETDATA.themes.current.dashboard_css,
5912             isAlreadyLoaded: function() { return false; }
5913         }
5914     ];
5915
5916     NETDATA.loadedRequiredJs = 0;
5917     NETDATA.loadRequiredJs = function(index, callback) {
5918         if(index >= NETDATA.requiredJs.length) {
5919             if(typeof callback === 'function')
5920                 callback();
5921             return;
5922         }
5923
5924         if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5925             NETDATA.loadedRequiredJs++;
5926             NETDATA.loadRequiredJs(++index, callback);
5927             return;
5928         }
5929
5930         if(NETDATA.options.debug.main_loop === true)
5931             console.log('loading ' + NETDATA.requiredJs[index].url);
5932
5933         var async = true;
5934         if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5935             async = false;
5936
5937         $.ajax({
5938             url: NETDATA.requiredJs[index].url,
5939             cache: true,
5940             dataType: "script",
5941             xhrFields: { withCredentials: true } // required for the cookie
5942         })
5943         .done(function() {
5944             if(NETDATA.options.debug.main_loop === true)
5945                 console.log('loaded ' + NETDATA.requiredJs[index].url);
5946         })
5947         .fail(function() {
5948             alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5949         })
5950         .always(function() {
5951             NETDATA.loadedRequiredJs++;
5952
5953             if(async === false)
5954                 NETDATA.loadRequiredJs(++index, callback);
5955         })
5956
5957         if(async === true)
5958             NETDATA.loadRequiredJs(++index, callback);
5959     };
5960
5961     NETDATA.loadRequiredCSS = function(index) {
5962         if(index >= NETDATA.requiredCSS.length)
5963             return;
5964
5965         if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5966             NETDATA.loadRequiredCSS(++index);
5967             return;
5968         }
5969
5970         if(NETDATA.options.debug.main_loop === true)
5971             console.log('loading ' + NETDATA.requiredCSS[index].url);
5972
5973         NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5974         NETDATA.loadRequiredCSS(++index);
5975     };
5976
5977
5978     // ----------------------------------------------------------------------------------------------------------------
5979     // Registry of netdata hosts
5980
5981     NETDATA.alarms = {
5982         onclick: null,                  // the callback to handle the click - it will be called with the alarm log entry
5983         chart_div_offset: 100,          // give that space above the chart when scrolling to it
5984         chart_div_id_prefix: 'chart_',  // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
5985         chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
5986
5987         ms_penalty: 0,                  // the time penalty of the next alarm
5988         ms_between_notifications: 500,  // firefox moves the alarms off-screen (above, outside the top of the screen)
5989                                         // if alarms are shown faster than: one per 500ms
5990
5991         notifications: false,           // when true, the browser supports notifications (may not be granted though)
5992         last_notification_id: 0,        // the id of the last alarm_log we have raised an alarm for
5993         first_notification_id: 0,       // the id of the first alarm_log entry for this session
5994                                         // this is used to prevent CLEAR notifications for past events
5995         // notifications_shown: new Array(),
5996
5997         server: null,                   // the server to connect to for fetching alarms
5998         current: null,                  // the list of raised alarms - updated in the background
5999         callback: null,                 // a callback function to call every time the list of raised alarms is refreshed
6000
6001         notify: function(entry) {
6002             // console.log('alarm ' + entry.unique_id);
6003
6004             if(entry.updated === true) {
6005                 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
6006                 return;
6007             }
6008
6009             var value = entry.value;
6010             if(NETDATA.alarms.current !== null) {
6011                 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
6012                 if(typeof t !== 'undefined' && entry.status == t.status)
6013                     value = t.value;
6014             }
6015
6016             var name = entry.name.replace(/_/g, ' ');
6017             var status = entry.status.toLowerCase();
6018             var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
6019             var tag = entry.alarm_id;
6020             var icon = 'images/seo-performance-128.png';
6021             var interaction = false;
6022             var data = entry;
6023             var show = true;
6024
6025             // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' +  entry.status);
6026
6027             switch(entry.status) {
6028                 case 'REMOVED':
6029                     show = false;
6030                     break;
6031
6032                 case 'UNDEFINED':
6033                     return;
6034
6035                 case 'UNINITIALIZED':
6036                     return;
6037
6038                 case 'CLEAR':
6039                     if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6040                         // console.log('alarm ' + entry.unique_id + ' is not current');
6041                         return;
6042                     }
6043                     if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6044                         // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6045                         return;
6046                     }
6047                     title = name + ' back to normal';
6048                     icon = 'images/check-mark-2-128-green.png'
6049                     interaction = false;
6050                     break;
6051
6052                 case 'WARNING':
6053                     if(entry.old_status === 'CRITICAL')
6054                         status = 'demoted to ' + entry.status.toLowerCase();
6055
6056                     icon = 'images/alert-128-orange.png';
6057                     interaction = false;
6058                     break;
6059
6060                 case 'CRITICAL':
6061                     if(entry.old_status === 'WARNING')
6062                         status = 'escalated to ' + entry.status.toLowerCase();
6063                     
6064                     icon = 'images/alert-128-red.png'
6065                     interaction = true;
6066                     break;
6067
6068                 default:
6069                     console.log('invalid alarm status ' + entry.status);
6070                     return;
6071             }
6072
6073             /*
6074             // cleanup old notifications with the same alarm_id as this one
6075             // FIXME: it does not seem to work on any web browser!
6076             var len = NETDATA.alarms.notifications_shown.length;
6077             while(len--) {
6078                 var n = NETDATA.alarms.notifications_shown[len];
6079                 if(n.data.alarm_id === entry.alarm_id) {
6080                     console.log('removing old alarm ' + n.data.unique_id);
6081
6082                     // close the notification
6083                     n.close.bind(n);
6084
6085                     // remove it from the array
6086                     NETDATA.alarms.notifications_shown.splice(len, 1);
6087                     len = NETDATA.alarms.notifications_shown.length;
6088                 }
6089             }
6090             */
6091
6092             if(show === true) {
6093
6094                 setTimeout(function() {
6095                     // show this notification
6096                     // console.log('new notification: ' + title);
6097                     var n = new Notification(title, {
6098                         body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6099                         tag: tag,
6100                         requireInteraction: interaction,
6101                         icon: NETDATA.serverDefault + icon,
6102                         data: data
6103                     });
6104
6105                     n.onclick = function(event) {
6106                         event.preventDefault();
6107                         NETDATA.alarms.onclick(event.target.data);
6108                     };
6109
6110                     // console.log(n);
6111                     // NETDATA.alarms.notifications_shown.push(n);
6112                     // console.log(entry);
6113                 }, NETDATA.alarms.ms_penalty);
6114
6115                 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6116             }
6117         },
6118
6119         scrollToChart: function(chart_id) {
6120             if(typeof chart_id === 'string') {
6121                 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6122                 if(typeof offset !== 'undefined') {
6123                     $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6124                     return true;
6125                 }
6126             }
6127             return false;
6128         },
6129
6130         scrollToAlarm: function(alarm) {
6131             if(typeof alarm === 'object') {
6132                 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6133
6134                 if(ret === true && NETDATA.options.page_is_visible === false)
6135                     window.focus();
6136                 //    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.');
6137             }
6138
6139         },
6140
6141         notifyAll: function() {
6142             // console.log('FETCHING ALARM LOG');
6143             NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6144                 // console.log('ALARM LOG FETCHED');
6145
6146                 if(data === null || typeof data !== 'object') {
6147                     console.log('invalid alarms log response');
6148                     return;
6149                 }
6150
6151                 if(data.length === 0) {
6152                     console.log('received empty alarm log');
6153                     return;
6154                 }
6155
6156                 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6157
6158                 data.sort(function(a, b) {
6159                     if(a.unique_id > b.unique_id) return -1;
6160                     if(a.unique_id < b.unique_id) return 1;
6161                     return 0;
6162                 });
6163
6164                 NETDATA.alarms.ms_penalty = 0;
6165
6166                 var len = data.length;
6167                 while(len--) {
6168                     if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6169                         NETDATA.alarms.notify(data[len]);
6170                     }
6171                     //else
6172                     //    console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6173                 }
6174
6175                 NETDATA.alarms.last_notification_id = data[0].unique_id;
6176                 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6177                 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6178             })
6179         },
6180
6181         check_notifications: function() {
6182             // returns true if we should fire 1+ notifications
6183
6184             if(NETDATA.alarms.notifications !== true) {
6185                 // console.log('notifications not available');
6186                 return false;
6187             }
6188
6189             if(Notification.permission !== 'granted') {
6190                 // console.log('notifications not granted');
6191                 return false;
6192             }
6193
6194             if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6195                 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6196
6197                 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6198                     // console.log('new alarms detected');
6199                     return true;
6200                 }
6201                 //else console.log('no new alarms');
6202             }
6203             // else console.log('cannot process alarms');
6204
6205             return false;
6206         },
6207
6208         get: function(what, callback) {
6209             $.ajax({
6210                 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6211                 async: true,
6212                 cache: false,
6213                 headers: {
6214                     'Cache-Control': 'no-cache, no-store',
6215                     'Pragma': 'no-cache'
6216                 },
6217                 xhrFields: { withCredentials: true } // required for the cookie
6218             })
6219                 .done(function(data) {
6220                     if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6221                         NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6222
6223                     if(typeof callback === 'function')
6224                         callback(data);
6225                 })
6226                 .fail(function() {
6227                     NETDATA.error(415, NETDATA.alarms.server);
6228
6229                     if(typeof callback === 'function')
6230                         callback(null);
6231                 });
6232         },
6233
6234         update_forever: function() {
6235             NETDATA.alarms.get('active', function(data) {
6236                 if(data !== null) {
6237                     NETDATA.alarms.current = data;
6238
6239                     if(NETDATA.alarms.check_notifications() === true) {
6240                         NETDATA.alarms.notifyAll();
6241                     }
6242
6243                     if (typeof NETDATA.alarms.callback === 'function') {
6244                         NETDATA.alarms.callback(data);
6245                     }
6246
6247                     // Health monitoring is disabled on this netdata
6248                     if(data.status === false) return;
6249                 }
6250
6251                 setTimeout(NETDATA.alarms.update_forever, 10000);
6252             });
6253         },
6254
6255         get_log: function(last_id, callback) {
6256             // console.log('fetching all log after ' + last_id.toString());
6257             $.ajax({
6258                 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6259                 async: true,
6260                 cache: false,
6261                 headers: {
6262                     'Cache-Control': 'no-cache, no-store',
6263                     'Pragma': 'no-cache'
6264                 },
6265                 xhrFields: { withCredentials: true } // required for the cookie
6266             })
6267                 .done(function(data) {
6268                     if(typeof callback === 'function')
6269                         callback(data);
6270                 })
6271                 .fail(function() {
6272                     NETDATA.error(416, NETDATA.alarms.server);
6273
6274                     if(typeof callback === 'function')
6275                         callback(null);
6276                 });
6277         },
6278
6279         init: function() {
6280             var host = NETDATA.serverDefault;
6281             while(host.slice(-1) === '/')
6282                 host = host.substring(0, host.length - 1);
6283             NETDATA.alarms.server = host;
6284
6285             NETDATA.alarms.last_notification_id = NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6286
6287             if(NETDATA.alarms.onclick === null)
6288                 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6289
6290             if(netdataShowAlarms === true) {
6291                 NETDATA.alarms.update_forever();
6292             
6293                 if('Notification' in window) {
6294                     // console.log('notifications available');
6295                     NETDATA.alarms.notifications = true;
6296
6297                     if(Notification.permission === 'default')
6298                         Notification.requestPermission();
6299                 }
6300             }
6301         }
6302     };
6303
6304     // ----------------------------------------------------------------------------------------------------------------
6305     // Registry of netdata hosts
6306
6307     NETDATA.registry = {
6308         server: null,       // the netdata registry server
6309         person_guid: null,  // the unique ID of this browser / user
6310         machine_guid: null, // the unique ID the netdata server that served dashboard.js
6311         hostname: null,     // the hostname of the netdata server that served dashboard.js
6312         machines: null,         // the user's other URLs
6313         machines_array: null,   // the user's other URLs in an array
6314         person_urls: null,
6315
6316         parsePersonUrls: function(person_urls) {
6317             // console.log(person_urls);
6318             NETDATA.registry.person_urls = person_urls;
6319
6320             if(person_urls) {
6321                 NETDATA.registry.machines = {};
6322                 NETDATA.registry.machines_array = new Array();
6323
6324                 var now = Date.now();
6325                 var apu = person_urls;
6326                 var i = apu.length;
6327                 while(i--) {
6328                     if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6329                         // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6330
6331                         var obj = {
6332                             guid: apu[i][0],
6333                             url: apu[i][1],
6334                             last_t: apu[i][2],
6335                             accesses: apu[i][3],
6336                             name: apu[i][4],
6337                             alternate_urls: new Array()
6338                         };
6339                         obj.alternate_urls.push(apu[i][1]);
6340
6341                         NETDATA.registry.machines[apu[i][0]] = obj;
6342                         NETDATA.registry.machines_array.push(obj);
6343                     }
6344                     else {
6345                         // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6346
6347                         var pu = NETDATA.registry.machines[apu[i][0]];
6348                         if(pu.last_t < apu[i][2]) {
6349                             pu.url = apu[i][1];
6350                             pu.last_t = apu[i][2];
6351                             pu.name = apu[i][4];
6352                         }
6353                         pu.accesses += apu[i][3];
6354                         pu.alternate_urls.push(apu[i][1]);
6355                     }
6356                 }
6357             }
6358
6359             if(typeof netdataRegistryCallback === 'function')
6360                 netdataRegistryCallback(NETDATA.registry.machines_array);
6361         },
6362
6363         init: function() {
6364             if(netdataRegistry !== true) return;
6365
6366             NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6367                 if(data) {
6368                     NETDATA.registry.server = data.registry;
6369                     NETDATA.registry.machine_guid = data.machine_guid;
6370                     NETDATA.registry.hostname = data.hostname;
6371
6372                     NETDATA.registry.access(2, function (person_urls) {
6373                         NETDATA.registry.parsePersonUrls(person_urls);
6374
6375                     });
6376                 }
6377             });
6378         },
6379
6380         hello: function(host, callback) {
6381             while(host.slice(-1) === '/')
6382                 host = host.substring(0, host.length - 1);
6383
6384             // send HELLO to a netdata server:
6385             // 1. verifies the server is reachable
6386             // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6387             $.ajax({
6388                     url: host + '/api/v1/registry?action=hello',
6389                     async: true,
6390                     cache: false,
6391                     headers: {
6392                         'Cache-Control': 'no-cache, no-store',
6393                         'Pragma': 'no-cache'
6394                     },
6395                     xhrFields: { withCredentials: true } // required for the cookie
6396                 })
6397                 .done(function(data) {
6398                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6399                         NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6400                         data = null;
6401                     }
6402
6403                     if(typeof callback === 'function')
6404                         callback(data);
6405                 })
6406                 .fail(function() {
6407                     NETDATA.error(407, host);
6408
6409                     if(typeof callback === 'function')
6410                         callback(null);
6411                 });
6412         },
6413
6414         access: function(max_redirects, callback) {
6415             // send ACCESS to a netdata registry:
6416             // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6417             // 2. it responds with a list of netdata servers we know
6418             // the registry identifies us using a cookie it sets the first time we access it
6419             // the registry may respond with a redirect URL to send us to another registry
6420             $.ajax({
6421                     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),
6422                     async: true,
6423                     cache: false,
6424                     headers: {
6425                         'Cache-Control': 'no-cache, no-store',
6426                         'Pragma': 'no-cache'
6427                     },
6428                     xhrFields: { withCredentials: true } // required for the cookie
6429                 })
6430                 .done(function(data) {
6431                     var redirect = null;
6432                     if(typeof data.registry === 'string')
6433                         redirect = data.registry;
6434
6435                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6436                         NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6437                         data = null;
6438                     }
6439
6440                     if(data === null) {
6441                         if(redirect !== null && max_redirects > 0) {
6442                             NETDATA.registry.server = redirect;
6443                             NETDATA.registry.access(max_redirects - 1, callback);
6444                         }
6445                         else {
6446                             if(typeof callback === 'function')
6447                                 callback(null);
6448                         }
6449                     }
6450                     else {
6451                         if(typeof data.person_guid === 'string')
6452                             NETDATA.registry.person_guid = data.person_guid;
6453
6454                         if(typeof callback === 'function')
6455                             callback(data.urls);
6456                     }
6457                 })
6458                 .fail(function() {
6459                     NETDATA.error(410, NETDATA.registry.server);
6460
6461                     if(typeof callback === 'function')
6462                         callback(null);
6463                 });
6464         },
6465
6466         delete: function(delete_url, callback) {
6467             // send DELETE to a netdata registry:
6468             $.ajax({
6469                 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),
6470                 async: true,
6471                 cache: false,
6472                 headers: {
6473                     'Cache-Control': 'no-cache, no-store',
6474                     'Pragma': 'no-cache'
6475                 },
6476                 xhrFields: { withCredentials: true } // required for the cookie
6477             })
6478                 .done(function(data) {
6479                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6480                         NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6481                         data = null;
6482                     }
6483
6484                     if(typeof callback === 'function')
6485                         callback(data);
6486                 })
6487                 .fail(function() {
6488                     NETDATA.error(412, NETDATA.registry.server);
6489
6490                     if(typeof callback === 'function')
6491                         callback(null);
6492                 });
6493         },
6494
6495         search: function(machine_guid, callback) {
6496             // SEARCH for the URLs of a machine:
6497             $.ajax({
6498                 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,
6499                 async: true,
6500                 cache: false,
6501                 headers: {
6502                     'Cache-Control': 'no-cache, no-store',
6503                     'Pragma': 'no-cache'
6504                 },
6505                 xhrFields: { withCredentials: true } // required for the cookie
6506             })
6507                 .done(function(data) {
6508                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6509                         NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6510                         data = null;
6511                     }
6512
6513                     if(typeof callback === 'function')
6514                         callback(data);
6515                 })
6516                 .fail(function() {
6517                     NETDATA.error(418, NETDATA.registry.server);
6518
6519                     if(typeof callback === 'function')
6520                         callback(null);
6521                 });
6522         },
6523
6524         switch: function(new_person_guid, callback) {
6525             // impersonate
6526             $.ajax({
6527                 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,
6528                 async: true,
6529                 cache: false,
6530                 headers: {
6531                     'Cache-Control': 'no-cache, no-store',
6532                     'Pragma': 'no-cache'
6533                 },
6534                 xhrFields: { withCredentials: true } // required for the cookie
6535             })
6536                 .done(function(data) {
6537                     if(typeof data.status !== 'string' || data.status !== 'ok') {
6538                         NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6539                         data = null;
6540                     }
6541
6542                     if(typeof callback === 'function')
6543                         callback(data);
6544                 })
6545                 .fail(function() {
6546                     NETDATA.error(414, NETDATA.registry.server);
6547
6548                     if(typeof callback === 'function')
6549                         callback(null);
6550                 });
6551         }
6552     };
6553
6554     // ----------------------------------------------------------------------------------------------------------------
6555     // Boot it!
6556
6557     if(typeof netdataPrepCallback === 'function')
6558         netdataPrepCallback();
6559
6560     NETDATA.errorReset();
6561     NETDATA.loadRequiredCSS(0);
6562
6563     NETDATA._loadjQuery(function() {
6564         NETDATA.loadRequiredJs(0, function() {
6565             if(typeof $().emulateTransitionEnd !== 'function') {
6566                 // bootstrap is not available
6567                 NETDATA.options.current.show_help = false;
6568             }
6569
6570             if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6571                 if(NETDATA.options.debug.main_loop === true)
6572                     console.log('starting chart refresh thread');
6573
6574                 NETDATA.start();
6575             }
6576         });
6577     });
6578
6579     // window.NETDATA = NETDATA;
6580 // })(window, document);