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