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