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