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