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