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