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