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