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