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