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