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