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