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