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