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