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