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