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