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