]> arthur.barton.de Git - netdata.git/blob - web/dashboard.js
my-netdata menu now finds the proper URL for a server; added example registry.html...
[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, dim, name, count) {
2153                                 var color = state._chartDimensionColor(name);
2154
2155                                 var user_element = null;
2156                                 var user_id = self.data('show-value-of-' + dim + '-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_ids[i], 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, dim, 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                 if(spotColor === 'disable') spotColor='';
3545                 if(minSpotColor === 'disable') minSpotColor='';
3546                 if(maxSpotColor === 'disable') maxSpotColor='';
3547
3548                 state.sparkline_options = {
3549                         type: type,
3550                         lineColor: lineColor,
3551                         fillColor: fillColor,
3552                         chartRangeMin: chartRangeMin,
3553                         chartRangeMax: chartRangeMax,
3554                         composite: composite,
3555                         enableTagOptions: enableTagOptions,
3556                         tagOptionPrefix: tagOptionPrefix,
3557                         tagValuesAttribute: tagValuesAttribute,
3558                         disableHiddenCheck: disableHiddenCheck,
3559                         defaultPixelsPerValue: defaultPixelsPerValue,
3560                         spotColor: spotColor,
3561                         minSpotColor: minSpotColor,
3562                         maxSpotColor: maxSpotColor,
3563                         spotRadius: spotRadius,
3564                         valueSpots: valueSpots,
3565                         highlightSpotColor: highlightSpotColor,
3566                         highlightLineColor: highlightLineColor,
3567                         lineWidth: lineWidth,
3568                         normalRangeMin: normalRangeMin,
3569                         normalRangeMax: normalRangeMax,
3570                         drawNormalOnTop: drawNormalOnTop,
3571                         xvalues: xvalues,
3572                         chartRangeClip: chartRangeClip,
3573                         chartRangeMinX: chartRangeMinX,
3574                         chartRangeMaxX: chartRangeMaxX,
3575                         disableInteraction: disableInteraction,
3576                         disableTooltips: disableTooltips,
3577                         disableHighlight: disableHighlight,
3578                         highlightLighten: highlightLighten,
3579                         highlightColor: highlightColor,
3580                         tooltipContainer: tooltipContainer,
3581                         tooltipClassname: tooltipClassname,
3582                         tooltipChartTitle: state.title,
3583                         tooltipFormat: tooltipFormat,
3584                         tooltipPrefix: tooltipPrefix,
3585                         tooltipSuffix: tooltipSuffix,
3586                         tooltipSkipNull: tooltipSkipNull,
3587                         tooltipValueLookups: tooltipValueLookups,
3588                         tooltipFormatFieldlist: tooltipFormatFieldlist,
3589                         tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3590                         numberFormatter: numberFormatter,
3591                         numberDigitGroupSep: numberDigitGroupSep,
3592                         numberDecimalMark: numberDecimalMark,
3593                         numberDigitGroupCount: numberDigitGroupCount,
3594                         animatedZooms: animatedZooms,
3595                         width: state.chartWidth(),
3596                         height: state.chartHeight()
3597                 };
3598
3599                 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3600                 return true;
3601         };
3602
3603         // ----------------------------------------------------------------------------------------------------------------
3604         // dygraph
3605
3606         NETDATA.dygraph = {
3607                 smooth: false
3608         };
3609
3610         NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3611                 if(after < state.netdata_first)
3612                         after = state.netdata_first;
3613
3614                 if(before > state.netdata_last)
3615                         before = state.netdata_last;
3616
3617                 state.setMode('zoom');
3618                 state.globalSelectionSyncStop();
3619                 state.globalSelectionSyncDelay();
3620                 state.dygraph_user_action = true;
3621                 state.dygraph_force_zoom = true;
3622                 state.updateChartPanOrZoom(after, before);
3623                 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3624         };
3625
3626         NETDATA.dygraphSetSelection = function(state, t) {
3627                 if(typeof state.dygraph_instance !== 'undefined') {
3628                         var r = state.calculateRowForTime(t);
3629                         if(r !== -1)
3630                                 state.dygraph_instance.setSelection(r);
3631                         else {
3632                                 state.dygraph_instance.clearSelection();
3633                                 state.legendShowUndefined();
3634                         }
3635                 }
3636
3637                 return true;
3638         };
3639
3640         NETDATA.dygraphClearSelection = function(state, t) {
3641                 if(typeof state.dygraph_instance !== 'undefined') {
3642                         state.dygraph_instance.clearSelection();
3643                 }
3644                 return true;
3645         };
3646
3647         NETDATA.dygraphSmoothInitialize = function(callback) {
3648                 $.ajax({
3649                         url: NETDATA.dygraph_smooth_js,
3650                         cache: true,
3651                         dataType: "script"
3652                 })
3653                 .done(function() {
3654                         NETDATA.dygraph.smooth = true;
3655                         smoothPlotter.smoothing = 0.3;
3656                 })
3657                 .fail(function() {
3658                         NETDATA.dygraph.smooth = false;
3659                 })
3660                 .always(function() {
3661                         if(typeof callback === "function")
3662                                 callback();
3663                 });
3664         };
3665
3666         NETDATA.dygraphInitialize = function(callback) {
3667                 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3668                         $.ajax({
3669                                 url: NETDATA.dygraph_js,
3670                                 cache: true,
3671                                 dataType: "script"
3672                         })
3673                         .done(function() {
3674                                 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3675                         })
3676                         .fail(function() {
3677                                 NETDATA.chartLibraries.dygraph.enabled = false;
3678                                 NETDATA.error(100, NETDATA.dygraph_js);
3679                         })
3680                         .always(function() {
3681                                 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3682                                         NETDATA.dygraphSmoothInitialize(callback);
3683                                 else if(typeof callback === "function")
3684                                         callback();
3685                         });
3686                 }
3687                 else {
3688                         NETDATA.chartLibraries.dygraph.enabled = false;
3689                         if(typeof callback === "function")
3690                                 callback();
3691                 }
3692         };
3693
3694         NETDATA.dygraphChartUpdate = function(state, data) {
3695                 var dygraph = state.dygraph_instance;
3696
3697                 if(typeof dygraph === 'undefined')
3698                         return NETDATA.dygraphChartCreate(state, data);
3699
3700                 // when the chart is not visible, and hidden
3701                 // if there is a window resize, dygraph detects
3702                 // its element size as 0x0.
3703                 // this will make it re-appear properly
3704
3705                 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3706                         dygraph.resize();
3707
3708                 var options = {
3709                                 file: data.result.data,
3710                                 colors: state.chartColors(),
3711                                 labels: data.result.labels,
3712                                 labelsDivWidth: state.chartWidth() - 70,
3713                                 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3714                 };
3715
3716                 if(state.dygraph_force_zoom === true) {
3717                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
3718                                 state.log('dygraphChartUpdate() forced zoom update');
3719
3720                         options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3721                         options.valueRange = state.dygraph_options.valueRange;
3722                         options.isZoomedIgnoreProgrammaticZoom = true;
3723                         state.dygraph_force_zoom = false;
3724                 }
3725                 else if(state.current.name !== 'auto') {
3726                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
3727                                 state.log('dygraphChartUpdate() loose update');
3728
3729                         options.valueRange = state.dygraph_options.valueRange;
3730                 }
3731                 else {
3732                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
3733                                 state.log('dygraphChartUpdate() strict update');
3734
3735                         options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3736                         options.valueRange = state.dygraph_options.valueRange;
3737                         options.isZoomedIgnoreProgrammaticZoom = true;
3738                 }
3739
3740                 if(state.dygraph_smooth_eligible === true) {
3741                         if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3742                                 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3743                                 NETDATA.dygraphChartCreate(state, data);
3744                                 return;
3745                         }
3746                 }
3747
3748                 dygraph.updateOptions(options);
3749
3750                 state.dygraph_last_rendered = new Date().getTime();
3751                 return true;
3752         };
3753
3754         NETDATA.dygraphChartCreate = function(state, data) {
3755                 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3756                         state.log('dygraphChartCreate()');
3757
3758                 var self = $(state.element);
3759
3760                 var chart_type = state.chart.chart_type;
3761                 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3762                 chart_type = self.data('dygraph-type') || chart_type;
3763
3764                 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3765                 smooth = self.data('dygraph-smooth') || smooth;
3766
3767                 if(NETDATA.dygraph.smooth === false)
3768                         smooth = false;
3769
3770                 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3771                 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3772
3773                 state.dygraph_options = {
3774                         colors: self.data('dygraph-colors') || state.chartColors(),
3775
3776                         // leave a few pixels empty on the right of the chart
3777                         rightGap: self.data('dygraph-rightgap') || 5,
3778                         showRangeSelector: self.data('dygraph-showrangeselector') || false,
3779                         showRoller: self.data('dygraph-showroller') || false,
3780
3781                         title: self.data('dygraph-title') || state.title,
3782                         titleHeight: self.data('dygraph-titleheight') || 19,
3783
3784                         legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3785                         labels: data.result.labels,
3786                         labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3787                         labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3788                         labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3789                         labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3790                         labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3791                         labelsKMB: false,
3792                         labelsKMG2: false,
3793                         showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3794                         hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3795
3796                         includeZero: self.data('dygraph-includezero') || false,
3797                         xRangePad: self.data('dygraph-xrangepad') || 0,
3798                         yRangePad: self.data('dygraph-yrangepad') || 1,
3799
3800                         valueRange: self.data('dygraph-valuerange') || null,
3801
3802                         ylabel: state.units,
3803                         yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3804
3805                         // the function to plot the chart
3806                         plotter: null,
3807
3808                         // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3809                         strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3810                         strokePattern: self.data('dygraph-strokepattern') || undefined,
3811
3812                         // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3813                         // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3814                         drawPoints: self.data('dygraph-drawpoints') || false,
3815
3816                         // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3817                         drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3818
3819                         connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3820                         pointSize: self.data('dygraph-pointsize') || 1,
3821
3822                         // enabling this makes the chart with little square lines
3823                         stepPlot: self.data('dygraph-stepplot') || false,
3824
3825                         // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3826                         strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3827                         strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3828
3829                         fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3830                         fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3831                         stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3832                         stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3833
3834                         drawAxis: self.data('dygraph-drawaxis') || true,
3835                         axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3836                         axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3837                         axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3838
3839                         drawGrid: self.data('dygraph-drawgrid') || true,
3840                         drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3841                         drawYGrid: self.data('dygraph-drawygrid') || undefined,
3842                         gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3843                         gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3844                         gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3845
3846                         maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3847                         sigFigs: self.data('dygraph-sigfigs') || null,
3848                         digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3849                         valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3850
3851                         highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3852                         highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3853                         highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3854
3855                         pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3856                         visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3857                         axes: {
3858                                 x: {
3859                                         pixelsPerLabel: 50,
3860                                         ticker: Dygraph.dateTicker,
3861                                         axisLabelFormatter: function (d, gran) {
3862                                                 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3863                                         },
3864                                         valueFormatter: function (ms) {
3865                                                 var d = new Date(ms);
3866                                                 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3867                                                 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3868                                         }
3869                                 },
3870                                 y: {
3871                                         pixelsPerLabel: 15,
3872                                         valueFormatter: function (x) {
3873                                                 // we format legends with the state object
3874                                                 // no need to do anything here
3875                                                 // return (Math.round(x*100) / 100).toLocaleString();
3876                                                 // return state.legendFormatValue(x);
3877                                                 return x;
3878                                         }
3879                                 }
3880                         },
3881                         legendFormatter: function(data) {
3882                                 var elements = state.element_legend_childs;
3883
3884                                 // if the hidden div is not there
3885                                 // we are not managing the legend
3886                                 if(elements.hidden === null) return;
3887
3888                                 if (typeof data.x !== 'undefined') {
3889                                         state.legendSetDate(data.x);
3890                                         var i = data.series.length;
3891                                         while(i--) {
3892                                                 var series = data.series[i];
3893                                                 if(!series.isVisible) continue;
3894                                                 state.legendSetLabelValue(series.label, series.y);
3895                                         }
3896                                 }
3897
3898                                 return '';
3899                         },
3900                         drawCallback: function(dygraph, is_initial) {
3901                                 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3902                                         state.dygraph_user_action = false;
3903
3904                                         var x_range = dygraph.xAxisRange();
3905                                         var after = Math.round(x_range[0]);
3906                                         var before = Math.round(x_range[1]);
3907
3908                                         if(NETDATA.options.debug.dygraph === true)
3909                                                 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3910
3911                                         if(before <= state.netdata_last && after >= state.netdata_first)
3912                                                 state.updateChartPanOrZoom(after, before);
3913                                 }
3914                         },
3915                         zoomCallback: function(minDate, maxDate, yRanges) {
3916                                 if(NETDATA.options.debug.dygraph === true)
3917                                         state.log('dygraphZoomCallback()');
3918
3919                                 state.globalSelectionSyncStop();
3920                                 state.globalSelectionSyncDelay();
3921                                 state.setMode('zoom');
3922
3923                                 // refresh it to the greatest possible zoom level
3924                                 state.dygraph_user_action = true;
3925                                 state.dygraph_force_zoom = true;
3926                                 state.updateChartPanOrZoom(minDate, maxDate);
3927                         },
3928                         highlightCallback: function(event, x, points, row, seriesName) {
3929                                 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3930                                         state.log('dygraphHighlightCallback()');
3931
3932                                 state.pauseChart();
3933
3934                                 // there is a bug in dygraph when the chart is zoomed enough
3935                                 // the time it thinks is selected is wrong
3936                                 // here we calculate the time t based on the row number selected
3937                                 // which is ok
3938                                 var t = state.data_after + row * state.data_update_every;
3939                                 // 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);
3940
3941                                 state.globalSelectionSync(x);
3942
3943                                 // fix legend zIndex using the internal structures of dygraph legend module
3944                                 // this works, but it is a hack!
3945                                 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
3946                         },
3947                         unhighlightCallback: function(event) {
3948                                 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3949                                         state.log('dygraphUnhighlightCallback()');
3950
3951                                 state.unpauseChart();
3952                                 state.globalSelectionSyncStop();
3953                         },
3954                         interactionModel : {
3955                                 mousedown: function(event, dygraph, context) {
3956                                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
3957                                                 state.log('interactionModel.mousedown()');
3958
3959                                         state.dygraph_user_action = true;
3960                                         state.globalSelectionSyncStop();
3961
3962                                         if(NETDATA.options.debug.dygraph === true)
3963                                                 state.log('dygraphMouseDown()');
3964
3965                                         // Right-click should not initiate a zoom.
3966                                         if(event.button && event.button === 2) return;
3967
3968                                         context.initializeMouseDown(event, dygraph, context);
3969
3970                                         if(event.button && event.button === 1) {
3971                                                 if (event.altKey || event.shiftKey) {
3972                                                         state.setMode('pan');
3973                                                         state.globalSelectionSyncDelay();
3974                                                         Dygraph.startPan(event, dygraph, context);
3975                                                 }
3976                                                 else {
3977                                                         state.setMode('zoom');
3978                                                         state.globalSelectionSyncDelay();
3979                                                         Dygraph.startZoom(event, dygraph, context);
3980                                                 }
3981                                         }
3982                                         else {
3983                                                 if (event.altKey || event.shiftKey) {
3984                                                         state.setMode('zoom');
3985                                                         state.globalSelectionSyncDelay();
3986                                                         Dygraph.startZoom(event, dygraph, context);
3987                                                 }
3988                                                 else {
3989                                                         state.setMode('pan');
3990                                                         state.globalSelectionSyncDelay();
3991                                                         Dygraph.startPan(event, dygraph, context);
3992                                                 }
3993                                         }
3994                                 },
3995                                 mousemove: function(event, dygraph, context) {
3996                                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
3997                                                 state.log('interactionModel.mousemove()');
3998
3999                                         if(context.isPanning) {
4000                                                 state.dygraph_user_action = true;
4001                                                 state.globalSelectionSyncStop();
4002                                                 state.globalSelectionSyncDelay();
4003                                                 state.setMode('pan');
4004                                                 Dygraph.movePan(event, dygraph, context);
4005                                         }
4006                                         else if(context.isZooming) {
4007                                                 state.dygraph_user_action = true;
4008                                                 state.globalSelectionSyncStop();
4009                                                 state.globalSelectionSyncDelay();
4010                                                 state.setMode('zoom');
4011                                                 Dygraph.moveZoom(event, dygraph, context);
4012                                         }
4013                                 },
4014                                 mouseup: function(event, dygraph, context) {
4015                                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
4016                                                 state.log('interactionModel.mouseup()');
4017
4018                                         if (context.isPanning) {
4019                                                 state.dygraph_user_action = true;
4020                                                 state.globalSelectionSyncDelay();
4021                                                 Dygraph.endPan(event, dygraph, context);
4022                                         }
4023                                         else if (context.isZooming) {
4024                                                 state.dygraph_user_action = true;
4025                                                 state.globalSelectionSyncDelay();
4026                                                 Dygraph.endZoom(event, dygraph, context);
4027                                         }
4028                                 },
4029                                 click: function(event, dygraph, context) {
4030                                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
4031                                                 state.log('interactionModel.click()');
4032
4033                                         event.preventDefault();
4034                                 },
4035                                 dblclick: function(event, dygraph, context) {
4036                                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
4037                                                 state.log('interactionModel.dblclick()');
4038                                         NETDATA.resetAllCharts(state);
4039                                 },
4040                                 mousewheel: function(event, dygraph, context) {
4041                                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
4042                                                 state.log('interactionModel.mousewheel()');
4043
4044                                         // Take the offset of a mouse event on the dygraph canvas and
4045                                         // convert it to a pair of percentages from the bottom left.
4046                                         // (Not top left, bottom is where the lower value is.)
4047                                         function offsetToPercentage(g, offsetX, offsetY) {
4048                                                 // This is calculating the pixel offset of the leftmost date.
4049                                                 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4050                                                 var yar0 = g.yAxisRange(0);
4051
4052                                                 // This is calculating the pixel of the higest value. (Top pixel)
4053                                                 var yOffset = g.toDomCoords(null, yar0[1])[1];
4054
4055                                                 // x y w and h are relative to the corner of the drawing area,
4056                                                 // so that the upper corner of the drawing area is (0, 0).
4057                                                 var x = offsetX - xOffset;
4058                                                 var y = offsetY - yOffset;
4059
4060                                                 // This is computing the rightmost pixel, effectively defining the
4061                                                 // width.
4062                                                 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4063
4064                                                 // This is computing the lowest pixel, effectively defining the height.
4065                                                 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4066
4067                                                 // Percentage from the left.
4068                                                 var xPct = w === 0 ? 0 : (x / w);
4069                                                 // Percentage from the top.
4070                                                 var yPct = h === 0 ? 0 : (y / h);
4071
4072                                                 // The (1-) part below changes it from "% distance down from the top"
4073                                                 // to "% distance up from the bottom".
4074                                                 return [xPct, (1-yPct)];
4075                                         }
4076
4077                                         // Adjusts [x, y] toward each other by zoomInPercentage%
4078                                         // Split it so the left/bottom axis gets xBias/yBias of that change and
4079                                         // tight/top gets (1-xBias)/(1-yBias) of that change.
4080                                         //
4081                                         // If a bias is missing it splits it down the middle.
4082                                         function zoomRange(g, zoomInPercentage, xBias, yBias) {
4083                                                 xBias = xBias || 0.5;
4084                                                 yBias = yBias || 0.5;
4085
4086                                                 function adjustAxis(axis, zoomInPercentage, bias) {
4087                                                         var delta = axis[1] - axis[0];
4088                                                         var increment = delta * zoomInPercentage;
4089                                                         var foo = [increment * bias, increment * (1-bias)];
4090
4091                                                         return [ axis[0] + foo[0], axis[1] - foo[1] ];
4092                                                 }
4093
4094                                                 var yAxes = g.yAxisRanges();
4095                                                 var newYAxes = [];
4096                                                 for (var i = 0; i < yAxes.length; i++) {
4097                                                         newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4098                                                 }
4099
4100                                                 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4101                                         }
4102
4103                                         if(event.altKey || event.shiftKey) {
4104                                                 state.dygraph_user_action = true;
4105
4106                                                 state.globalSelectionSyncStop();
4107                                                 state.globalSelectionSyncDelay();
4108
4109                                                 // http://dygraphs.com/gallery/interaction-api.js
4110                                                 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4111                                                 var percentage = normal / 50;
4112
4113                                                 if (!(event.offsetX && event.offsetY)){
4114                                                         event.offsetX = event.layerX - event.target.offsetLeft;
4115                                                         event.offsetY = event.layerY - event.target.offsetTop;
4116                                                 }
4117
4118                                                 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4119                                                 var xPct = percentages[0];
4120                                                 var yPct = percentages[1];
4121
4122                                                 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4123
4124                                                 var after = new_x_range[0];
4125                                                 var before = new_x_range[1];
4126
4127                                                 var first = state.netdata_first + state.data_update_every;
4128                                                 var last = state.netdata_last + state.data_update_every;
4129
4130                                                 if(before > last) {
4131                                                         after -= (before - last);
4132                                                         before = last;
4133                                                 }
4134                                                 if(after < first) {
4135                                                         after = first;
4136                                                 }
4137
4138                                                 state.setMode('zoom');
4139                                                 if(state.updateChartPanOrZoom(after, before) === true)
4140                                                         dygraph.updateOptions({ dateWindow: [ after, before ] });
4141
4142                                                 event.preventDefault();
4143                                         }
4144                                 },
4145                                 touchstart: function(event, dygraph, context) {
4146                                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
4147                                                 state.log('interactionModel.touchstart()');
4148
4149                                         state.dygraph_user_action = true;
4150                                         state.setMode('zoom');
4151                                         state.pauseChart();
4152
4153                                         Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4154
4155                                         // we overwrite the touch directions at the end, to overwrite
4156                                         // the internal default of dygraphs
4157                                         context.touchDirections = { x: true, y: false };
4158
4159                                         state.dygraph_last_touch_start = new Date().getTime();
4160                                         state.dygraph_last_touch_move = 0;
4161
4162                                         if(typeof event.touches[0].pageX === 'number')
4163                                                 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4164                                         else
4165                                                 state.dygraph_last_touch_page_x = 0;
4166                                 },
4167                                 touchmove: function(event, dygraph, context) {
4168                                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
4169                                                 state.log('interactionModel.touchmove()');
4170
4171                                         state.dygraph_user_action = true;
4172                                         Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4173
4174                                         state.dygraph_last_touch_move = new Date().getTime();
4175                                 },
4176                                 touchend: function(event, dygraph, context) {
4177                                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
4178                                                 state.log('interactionModel.touchend()');
4179
4180                                         state.dygraph_user_action = true;
4181                                         Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4182
4183                                         // if it didn't move, it is a selection
4184                                         if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4185                                                 // internal api of dygraphs
4186                                                 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4187                                                 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4188                                                 if(NETDATA.dygraphSetSelection(state, t) === true)
4189                                                         state.globalSelectionSync(t);
4190                                         }
4191
4192                                         // if it was double tap within double click time, reset the charts
4193                                         var now = new Date().getTime();
4194                                         if(typeof state.dygraph_last_touch_end !== 'undefined') {
4195                                                 if(state.dygraph_last_touch_move === 0) {
4196                                                         var dt = now - state.dygraph_last_touch_end;
4197                                                         if(dt <= NETDATA.options.current.double_click_speed)
4198                                                                 NETDATA.resetAllCharts(state);
4199                                                 }
4200                                         }
4201
4202                                         // remember the timestamp of the last touch end
4203                                         state.dygraph_last_touch_end = now;
4204                                 }
4205                         }
4206                 };
4207
4208                 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4209                         state.dygraph_options.drawGrid = false;
4210                         state.dygraph_options.drawAxis = false;
4211                         state.dygraph_options.title = undefined;
4212                         state.dygraph_options.units = undefined;
4213                         state.dygraph_options.ylabel = undefined;
4214                         state.dygraph_options.yLabelWidth = 0;
4215                         state.dygraph_options.labelsDivWidth = 120;
4216                         state.dygraph_options.labelsDivStyles.width = '120px';
4217                         state.dygraph_options.labelsSeparateLines = true;
4218                         state.dygraph_options.rightGap = 0;
4219                         state.dygraph_options.yRangePad = 1;
4220                 }
4221
4222                 if(smooth === true) {
4223                         state.dygraph_smooth_eligible = true;
4224
4225                         if(NETDATA.options.current.smooth_plot === true)
4226                                 state.dygraph_options.plotter = smoothPlotter;
4227                 }
4228                 else state.dygraph_smooth_eligible = false;
4229
4230                 state.dygraph_instance = new Dygraph(state.element_chart,
4231                         data.result.data, state.dygraph_options);
4232
4233                 state.dygraph_force_zoom = false;
4234                 state.dygraph_user_action = false;
4235                 state.dygraph_last_rendered = new Date().getTime();
4236                 return true;
4237         };
4238
4239         // ----------------------------------------------------------------------------------------------------------------
4240         // morris
4241
4242         NETDATA.morrisInitialize = function(callback) {
4243                 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4244
4245                         // morris requires raphael
4246                         if(!NETDATA.chartLibraries.raphael.initialized) {
4247                                 if(NETDATA.chartLibraries.raphael.enabled) {
4248                                         NETDATA.raphaelInitialize(function() {
4249                                                 NETDATA.morrisInitialize(callback);
4250                                         });
4251                                 }
4252                                 else {
4253                                         NETDATA.chartLibraries.morris.enabled = false;
4254                                         if(typeof callback === "function")
4255                                                 callback();
4256                                 }
4257                         }
4258                         else {
4259                                 NETDATA._loadCSS(NETDATA.morris_css);
4260
4261                                 $.ajax({
4262                                         url: NETDATA.morris_js,
4263                                         cache: true,
4264                                         dataType: "script"
4265                                 })
4266                                 .done(function() {
4267                                         NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4268                                 })
4269                                 .fail(function() {
4270                                         NETDATA.chartLibraries.morris.enabled = false;
4271                                         NETDATA.error(100, NETDATA.morris_js);
4272                                 })
4273                                 .always(function() {
4274                                         if(typeof callback === "function")
4275                                                 callback();
4276                                 });
4277                         }
4278                 }
4279                 else {
4280                         NETDATA.chartLibraries.morris.enabled = false;
4281                         if(typeof callback === "function")
4282                                 callback();
4283                 }
4284         };
4285
4286         NETDATA.morrisChartUpdate = function(state, data) {
4287                 state.morris_instance.setData(data.result.data);
4288                 return true;
4289         };
4290
4291         NETDATA.morrisChartCreate = function(state, data) {
4292
4293                 state.morris_options = {
4294                                 element: state.element_chart.id,
4295                                 data: data.result.data,
4296                                 xkey: 'time',
4297                                 ykeys: data.dimension_names,
4298                                 labels: data.dimension_names,
4299                                 lineWidth: 2,
4300                                 pointSize: 3,
4301                                 smooth: true,
4302                                 hideHover: 'auto',
4303                                 parseTime: true,
4304                                 continuousLine: false,
4305                                 behaveLikeLine: false
4306                 };
4307
4308                 if(state.chart.chart_type === 'line')
4309                         state.morris_instance = new Morris.Line(state.morris_options);
4310
4311                 else if(state.chart.chart_type === 'area') {
4312                         state.morris_options.behaveLikeLine = true;
4313                         state.morris_instance = new Morris.Area(state.morris_options);
4314                 }
4315                 else // stacked
4316                         state.morris_instance = new Morris.Area(state.morris_options);
4317
4318                 return true;
4319         };
4320
4321         // ----------------------------------------------------------------------------------------------------------------
4322         // raphael
4323
4324         NETDATA.raphaelInitialize = function(callback) {
4325                 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4326                         $.ajax({
4327                                 url: NETDATA.raphael_js,
4328                                 cache: true,
4329                                 dataType: "script"
4330                         })
4331                         .done(function() {
4332                                 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4333                         })
4334                         .fail(function() {
4335                                 NETDATA.chartLibraries.raphael.enabled = false;
4336                                 NETDATA.error(100, NETDATA.raphael_js);
4337                         })
4338                         .always(function() {
4339                                 if(typeof callback === "function")
4340                                         callback();
4341                         });
4342                 }
4343                 else {
4344                         NETDATA.chartLibraries.raphael.enabled = false;
4345                         if(typeof callback === "function")
4346                                 callback();
4347                 }
4348         };
4349
4350         NETDATA.raphaelChartUpdate = function(state, data) {
4351                 $(state.element_chart).raphael(data.result, {
4352                         width: state.chartWidth(),
4353                         height: state.chartHeight()
4354                 });
4355
4356                 return false;
4357         };
4358
4359         NETDATA.raphaelChartCreate = function(state, data) {
4360                 $(state.element_chart).raphael(data.result, {
4361                         width: state.chartWidth(),
4362                         height: state.chartHeight()
4363                 });
4364
4365                 return false;
4366         };
4367
4368         // ----------------------------------------------------------------------------------------------------------------
4369         // C3
4370
4371         NETDATA.c3Initialize = function(callback) {
4372                 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4373
4374                         // C3 requires D3
4375                         if(!NETDATA.chartLibraries.d3.initialized) {
4376                                 if(NETDATA.chartLibraries.d3.enabled) {
4377                                         NETDATA.d3Initialize(function() {
4378                                                 NETDATA.c3Initialize(callback);
4379                                         });
4380                                 }
4381                                 else {
4382                                         NETDATA.chartLibraries.c3.enabled = false;
4383                                         if(typeof callback === "function")
4384                                                 callback();
4385                                 }
4386                         }
4387                         else {
4388                                 NETDATA._loadCSS(NETDATA.c3_css);
4389
4390                                 $.ajax({
4391                                         url: NETDATA.c3_js,
4392                                         cache: true,
4393                                         dataType: "script"
4394                                 })
4395                                 .done(function() {
4396                                         NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4397                                 })
4398                                 .fail(function() {
4399                                         NETDATA.chartLibraries.c3.enabled = false;
4400                                         NETDATA.error(100, NETDATA.c3_js);
4401                                 })
4402                                 .always(function() {
4403                                         if(typeof callback === "function")
4404                                                 callback();
4405                                 });
4406                         }
4407                 }
4408                 else {
4409                         NETDATA.chartLibraries.c3.enabled = false;
4410                         if(typeof callback === "function")
4411                                 callback();
4412                 }
4413         };
4414
4415         NETDATA.c3ChartUpdate = function(state, data) {
4416                 state.c3_instance.destroy();
4417                 return NETDATA.c3ChartCreate(state, data);
4418
4419                 //state.c3_instance.load({
4420                 //      rows: data.result,
4421                 //      unload: true
4422                 //});
4423
4424                 //return true;
4425         };
4426
4427         NETDATA.c3ChartCreate = function(state, data) {
4428
4429                 state.element_chart.id = 'c3-' + state.uuid;
4430                 // console.log('id = ' + state.element_chart.id);
4431
4432                 state.c3_instance = c3.generate({
4433                         bindto: '#' + state.element_chart.id,
4434                         size: {
4435                                 width: state.chartWidth(),
4436                                 height: state.chartHeight()
4437                         },
4438                         color: {
4439                                 pattern: state.chartColors()
4440                         },
4441                         data: {
4442                                 x: 'time',
4443                                 rows: data.result,
4444                                 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4445                         },
4446                         axis: {
4447                                 x: {
4448                                         type: 'timeseries',
4449                                         tick: {
4450                                                 format: function(x) {
4451                                                         return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4452                                                 }
4453                                         }
4454                                 }
4455                         },
4456                         grid: {
4457                                 x: {
4458                                         show: true
4459                                 },
4460                                 y: {
4461                                         show: true
4462                                 }
4463                         },
4464                         point: {
4465                                 show: false
4466                         },
4467                         line: {
4468                                 connectNull: false
4469                         },
4470                         transition: {
4471                                 duration: 0
4472                         },
4473                         interaction: {
4474                                 enabled: true
4475                         }
4476                 });
4477
4478                 // console.log(state.c3_instance);
4479
4480                 return true;
4481         };
4482
4483         // ----------------------------------------------------------------------------------------------------------------
4484         // D3
4485
4486         NETDATA.d3Initialize = function(callback) {
4487                 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4488                         $.ajax({
4489                                 url: NETDATA.d3_js,
4490                                 cache: true,
4491                                 dataType: "script"
4492                         })
4493                         .done(function() {
4494                                 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4495                         })
4496                         .fail(function() {
4497                                 NETDATA.chartLibraries.d3.enabled = false;
4498                                 NETDATA.error(100, NETDATA.d3_js);
4499                         })
4500                         .always(function() {
4501                                 if(typeof callback === "function")
4502                                         callback();
4503                         });
4504                 }
4505                 else {
4506                         NETDATA.chartLibraries.d3.enabled = false;
4507                         if(typeof callback === "function")
4508                                 callback();
4509                 }
4510         };
4511
4512         NETDATA.d3ChartUpdate = function(state, data) {
4513                 return false;
4514         };
4515
4516         NETDATA.d3ChartCreate = function(state, data) {
4517                 return false;
4518         };
4519
4520         // ----------------------------------------------------------------------------------------------------------------
4521         // google charts
4522
4523         NETDATA.googleInitialize = function(callback) {
4524                 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4525                         $.ajax({
4526                                 url: NETDATA.google_js,
4527                                 cache: true,
4528                                 dataType: "script"
4529                         })
4530                         .done(function() {
4531                                 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4532                                 google.load('visualization', '1.1', {
4533                                         'packages': ['corechart', 'controls'],
4534                                         'callback': callback
4535                                 });
4536                         })
4537                         .fail(function() {
4538                                 NETDATA.chartLibraries.google.enabled = false;
4539                                 NETDATA.error(100, NETDATA.google_js);
4540                                 if(typeof callback === "function")
4541                                         callback();
4542                         });
4543                 }
4544                 else {
4545                         NETDATA.chartLibraries.google.enabled = false;
4546                         if(typeof callback === "function")
4547                                 callback();
4548                 }
4549         };
4550
4551         NETDATA.googleChartUpdate = function(state, data) {
4552                 var datatable = new google.visualization.DataTable(data.result);
4553                 state.google_instance.draw(datatable, state.google_options);
4554                 return true;
4555         };
4556
4557         NETDATA.googleChartCreate = function(state, data) {
4558                 var datatable = new google.visualization.DataTable(data.result);
4559
4560                 state.google_options = {
4561                         colors: state.chartColors(),
4562
4563                         // do not set width, height - the chart resizes itself
4564                         //width: state.chartWidth(),
4565                         //height: state.chartHeight(),
4566                         lineWidth: 1,
4567                         title: state.title,
4568                         fontSize: 11,
4569                         hAxis: {
4570                         //      title: "Time of Day",
4571                         //      format:'HH:mm:ss',
4572                                 viewWindowMode: 'maximized',
4573                                 slantedText: false,
4574                                 format:'HH:mm:ss',
4575                                 textStyle: {
4576                                         fontSize: 9
4577                                 },
4578                                 gridlines: {
4579                                         color: '#EEE'
4580                                 }
4581                         },
4582                         vAxis: {
4583                                 title: state.units,
4584                                 viewWindowMode: 'pretty',
4585                                 minValue: -0.1,
4586                                 maxValue: 0.1,
4587                                 direction: 1,
4588                                 textStyle: {
4589                                         fontSize: 9
4590                                 },
4591                                 gridlines: {
4592                                         color: '#EEE'
4593                                 }
4594                         },
4595                         chartArea: {
4596                                 width: '65%',
4597                                 height: '80%'
4598                         },
4599                         focusTarget: 'category',
4600                         annotation: {
4601                                 '1': {
4602                                         style: 'line'
4603                                 }
4604                         },
4605                         pointsVisible: 0,
4606                         titlePosition: 'out',
4607                         titleTextStyle: {
4608                                 fontSize: 11
4609                         },
4610                         tooltip: {
4611                                 isHtml: false,
4612                                 ignoreBounds: true,
4613                                 textStyle: {
4614                                         fontSize: 9
4615                                 }
4616                         },
4617                         curveType: 'function',
4618                         areaOpacity: 0.3,
4619                         isStacked: false
4620                 };
4621
4622                 switch(state.chart.chart_type) {
4623                         case "area":
4624                                 state.google_options.vAxis.viewWindowMode = 'maximized';
4625                                 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4626                                 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4627                                 break;
4628
4629                         case "stacked":
4630                                 state.google_options.isStacked = true;
4631                                 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4632                                 state.google_options.vAxis.viewWindowMode = 'maximized';
4633                                 state.google_options.vAxis.minValue = null;
4634                                 state.google_options.vAxis.maxValue = null;
4635                                 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4636                                 break;
4637
4638                         default:
4639                         case "line":
4640                                 state.google_options.lineWidth = 2;
4641                                 state.google_instance = new google.visualization.LineChart(state.element_chart);
4642                                 break;
4643                 }
4644
4645                 state.google_instance.draw(datatable, state.google_options);
4646                 return true;
4647         };
4648
4649         // ----------------------------------------------------------------------------------------------------------------
4650
4651         NETDATA.percentFromValueMax = function(value, max) {
4652                 if(value === null) value = 0;
4653                 if(max < value) max = value;
4654
4655                 var pcent = 0;
4656                 if(max !== 0) {
4657                         pcent = Math.round(value * 100 / max);
4658                         if(pcent === 0 && value > 0) pcent = 1;
4659                 }
4660
4661                 return pcent;
4662         };
4663
4664         // ----------------------------------------------------------------------------------------------------------------
4665         // easy-pie-chart
4666
4667         NETDATA.easypiechartInitialize = function(callback) {
4668                 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4669                         $.ajax({
4670                                 url: NETDATA.easypiechart_js,
4671                                 cache: true,
4672                                 dataType: "script"
4673                         })
4674                                 .done(function() {
4675                                         NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4676                                 })
4677                                 .fail(function() {
4678                                         NETDATA.chartLibraries.easypiechart.enabled = false;
4679                                         NETDATA.error(100, NETDATA.easypiechart_js);
4680                                 })
4681                                 .always(function() {
4682                                         if(typeof callback === "function")
4683                                                 callback();
4684                                 })
4685                 }
4686                 else {
4687                         NETDATA.chartLibraries.easypiechart.enabled = false;
4688                         if(typeof callback === "function")
4689                                 callback();
4690                 }
4691         };
4692
4693         NETDATA.easypiechartClearSelection = function(state) {
4694                 if(typeof state.easyPieChartEvent !== 'undefined') {
4695                         if(state.easyPieChartEvent.timer !== null)
4696                                 clearTimeout(state.easyPieChartEvent.timer);
4697
4698                         state.easyPieChartEvent.timer = null;
4699                 }
4700
4701                 if(state.isAutoRefreshable() === true && state.data !== null) {
4702                         NETDATA.easypiechartChartUpdate(state, state.data);
4703                 }
4704                 else {
4705                         state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4706                         state.easyPieChart_instance.update(0);
4707                 }
4708                 state.easyPieChart_instance.enableAnimation();
4709
4710                 return true;
4711         };
4712
4713         NETDATA.easypiechartSetSelection = function(state, t) {
4714                 if(state.timeIsVisible(t) !== true)
4715                         return NETDATA.easypiechartClearSelection(state);
4716
4717                 var slot = state.calculateRowForTime(t);
4718                 if(slot < 0 || slot >= state.data.result.length)
4719                         return NETDATA.easypiechartClearSelection(state);
4720
4721                 if(typeof state.easyPieChartEvent === 'undefined') {
4722                         state.easyPieChartEvent = {
4723                                 timer: null,
4724                                 value: 0,
4725                                 pcent: 0
4726                         };
4727                 }
4728
4729                 var value = state.data.result[state.data.result.length - 1 - slot];
4730                 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4731                 var pcent = NETDATA.percentFromValueMax(value, max);
4732
4733                 state.easyPieChartEvent.value = value;
4734                 state.easyPieChartEvent.pcent = pcent;
4735                 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4736
4737                 if(state.easyPieChartEvent.timer === null) {
4738                         state.easyPieChart_instance.disableAnimation();
4739
4740                         state.easyPieChartEvent.timer = setTimeout(function() {
4741                                 state.easyPieChartEvent.timer = null;
4742                                 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4743                         }, NETDATA.options.current.charts_selection_animation_delay);
4744                 }
4745
4746                 return true;
4747         };
4748
4749         NETDATA.easypiechartChartUpdate = function(state, data) {
4750                 var value, max, pcent;
4751
4752                 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
4753                         value = null;
4754                         max = 0;
4755                         pcent = 0;
4756                 }
4757                 else {
4758                         value = data.result[0];
4759                         max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4760                         pcent = NETDATA.percentFromValueMax(value, max);
4761                 }
4762
4763                 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4764                 state.easyPieChart_instance.update(pcent);
4765                 return true;
4766         };
4767
4768         NETDATA.easypiechartChartCreate = function(state, data) {
4769                 var self = $(state.element);
4770                 var chart = $(state.element_chart);
4771
4772                 var value = data.result[0];
4773                 var max = self.data('easypiechart-max-value') || null;
4774                 var adjust = self.data('easypiechart-adjust') || null;
4775
4776                 if(max === null) {
4777                         max = data.max;
4778                         state.easyPieChartMax = null;
4779                 }
4780                 else
4781                         state.easyPieChartMax = max;
4782
4783                 var pcent = NETDATA.percentFromValueMax(value, max);
4784
4785                 chart.data('data-percent', pcent);
4786
4787                 var size;
4788                 switch(adjust) {
4789                         case 'width': size = state.chartHeight(); break;
4790                         case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4791                         case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4792                         case 'height':
4793                         default: size = state.chartWidth(); break;
4794                 }
4795                 state.element.style.width = size + 'px';
4796                 state.element.style.height = size + 'px';
4797
4798                 var stroke = Math.floor(size / 22);
4799                 if(stroke < 3) stroke = 2;
4800
4801                 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4802                 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4803                 state.easyPieChartLabel = document.createElement('span');
4804                 state.easyPieChartLabel.className = 'easyPieChartLabel';
4805                 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4806                 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4807                 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4808                 state.element_chart.appendChild(state.easyPieChartLabel);
4809
4810                 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4811                 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4812                 state.easyPieChartTitle = document.createElement('span');
4813                 state.easyPieChartTitle.className = 'easyPieChartTitle';
4814                 state.easyPieChartTitle.innerHTML = state.title;
4815                 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4816                 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4817                 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4818                 state.element_chart.appendChild(state.easyPieChartTitle);
4819
4820                 var unitfontsize = Math.round(titlefontsize * 0.9);
4821                 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4822                 state.easyPieChartUnits = document.createElement('span');
4823                 state.easyPieChartUnits.className = 'easyPieChartUnits';
4824                 state.easyPieChartUnits.innerHTML = state.units;
4825                 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4826                 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4827                 state.element_chart.appendChild(state.easyPieChartUnits);
4828
4829                 chart.easyPieChart({
4830                         barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4831                         trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4832                         scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4833                         scaleLength: self.data('easypiechart-scalelength') || 5,
4834                         lineCap: self.data('easypiechart-linecap') || 'round',
4835                         lineWidth: self.data('easypiechart-linewidth') || stroke,
4836                         trackWidth: self.data('easypiechart-trackwidth') || undefined,
4837                         size: self.data('easypiechart-size') || size,
4838                         rotate: self.data('easypiechart-rotate') || 0,
4839                         animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4840                         easing: self.data('easypiechart-easing') || undefined
4841                 });
4842
4843                 // when we just re-create the chart
4844                 // do not animate the first update
4845                 var animate = true;
4846                 if(typeof state.easyPieChart_instance !== 'undefined')
4847                         animate = false;
4848
4849                 state.easyPieChart_instance = chart.data('easyPieChart');
4850                 if(animate === false) state.easyPieChart_instance.disableAnimation();
4851                 state.easyPieChart_instance.update(pcent);
4852                 if(animate === false) state.easyPieChart_instance.enableAnimation();
4853                 return true;
4854         };
4855
4856         // ----------------------------------------------------------------------------------------------------------------
4857         // gauge.js
4858
4859         NETDATA.gaugeInitialize = function(callback) {
4860                 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4861                         $.ajax({
4862                                 url: NETDATA.gauge_js,
4863                                 cache: true,
4864                                 dataType: "script"
4865                         })
4866                                 .done(function() {
4867                                         NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4868                                 })
4869                                 .fail(function() {
4870                                         NETDATA.chartLibraries.gauge.enabled = false;
4871                                         NETDATA.error(100, NETDATA.gauge_js);
4872                                 })
4873                                 .always(function() {
4874                                         if(typeof callback === "function")
4875                                                 callback();
4876                                 })
4877                 }
4878                 else {
4879                         NETDATA.chartLibraries.gauge.enabled = false;
4880                         if(typeof callback === "function")
4881                                 callback();
4882                 }
4883         };
4884
4885         NETDATA.gaugeAnimation = function(state, status) {
4886                 var speed = 32;
4887
4888                 if(typeof status === 'boolean' && status === false)
4889                         speed = 1000000000;
4890                 else if(typeof status === 'number')
4891                         speed = status;
4892
4893                 state.gauge_instance.animationSpeed = speed;
4894                 state.___gaugeOld__.speed = speed;
4895         };
4896
4897         NETDATA.gaugeSet = function(state, value, min, max) {
4898                 if(typeof value !== 'number') value = 0;
4899                 if(typeof min !== 'number') min = 0;
4900                 if(typeof max !== 'number') max = 0;
4901                 if(value > max) max = value;
4902                 if(value < min) min = value;
4903                 if(min > max) {
4904                         var t = min;
4905                         min = max;
4906                         max = t;
4907                 }
4908                 else if(min == max)
4909                         max = min + 1;
4910
4911                 // gauge.js has an issue if the needle
4912                 // is smaller than min or larger than max
4913                 // when we set the new values
4914                 // the needle will go crazy
4915
4916                 // to prevent it, we always feed it
4917                 // with a percentage, so that the needle
4918                 // is always between min and max
4919                 var pcent = (value - min) * 100 / (max - min);
4920
4921                 // these should never happen
4922                 if(pcent < 0) pcent = 0;
4923                 if(pcent > 100) pcent = 100;
4924
4925                 state.gauge_instance.set(pcent);
4926
4927                 state.___gaugeOld__.value = value;
4928                 state.___gaugeOld__.min = min;
4929                 state.___gaugeOld__.max = max;
4930         };
4931
4932         NETDATA.gaugeSetLabels = function(state, value, min, max) {
4933                 if(state.___gaugeOld__.valueLabel !== value) {
4934                         state.___gaugeOld__.valueLabel = value;
4935                         state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
4936                 }
4937                 if(state.___gaugeOld__.minLabel !== min) {
4938                         state.___gaugeOld__.minLabel = min;
4939                         state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
4940                 }
4941                 if(state.___gaugeOld__.maxLabel !== max) {
4942                         state.___gaugeOld__.maxLabel = max;
4943                         state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
4944                 }
4945         };
4946
4947         NETDATA.gaugeClearSelection = function(state) {
4948                 if(typeof state.gaugeEvent !== 'undefined') {
4949                         if(state.gaugeEvent.timer !== null)
4950                                 clearTimeout(state.gaugeEvent.timer);
4951
4952                         state.gaugeEvent.timer = null;
4953                 }
4954
4955                 if(state.isAutoRefreshable() === true && state.data !== null) {
4956                         NETDATA.gaugeChartUpdate(state, state.data);
4957                 }
4958                 else {
4959                         NETDATA.gaugeAnimation(state, false);
4960                         NETDATA.gaugeSet(state, null, null, null);
4961                         NETDATA.gaugeSetLabels(state, null, null, null);
4962                 }
4963
4964                 NETDATA.gaugeAnimation(state, true);
4965                 return true;
4966         };
4967
4968         NETDATA.gaugeSetSelection = function(state, t) {
4969                 if(state.timeIsVisible(t) !== true)
4970                         return NETDATA.gaugeClearSelection(state);
4971
4972                 var slot = state.calculateRowForTime(t);
4973                 if(slot < 0 || slot >= state.data.result.length)
4974                         return NETDATA.gaugeClearSelection(state);
4975
4976                 if(typeof state.gaugeEvent === 'undefined') {
4977                         state.gaugeEvent = {
4978                                 timer: null,
4979                                 value: 0,
4980                                 min: 0,
4981                                 max: 0
4982                         };
4983                 }
4984
4985                 var value = state.data.result[state.data.result.length - 1 - slot];
4986                 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
4987                 var min = 0;
4988
4989                 state.gaugeEvent.value = value;
4990                 state.gaugeEvent.max = max;
4991                 state.gaugeEvent.min = min;
4992                 NETDATA.gaugeSetLabels(state, value, min, max);
4993
4994                 if(state.gaugeEvent.timer === null) {
4995                         NETDATA.gaugeAnimation(state, false);
4996
4997                         state.gaugeEvent.timer = setTimeout(function() {
4998                                 state.gaugeEvent.timer = null;
4999                                 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5000                         }, NETDATA.options.current.charts_selection_animation_delay);
5001                 }
5002
5003                 return true;
5004         };
5005
5006         NETDATA.gaugeChartUpdate = function(state, data) {
5007                 var value, min, max;
5008
5009                 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5010                         value = 0;
5011                         min = 0;
5012                         max = 1;
5013                         NETDATA.gaugeSetLabels(state, null, null, null);
5014                 }
5015                 else {
5016                         value = data.result[0];
5017                         min = 0;
5018                         max = (state.gaugeMax === null)?data.max:state.gaugeMax;
5019                         if(value > max) max = value;
5020                         NETDATA.gaugeSetLabels(state, value, min, max);
5021                 }
5022
5023                 NETDATA.gaugeSet(state, value, min, max);
5024                 return true;
5025         };
5026
5027         NETDATA.gaugeChartCreate = function(state, data) {
5028                 var self = $(state.element);
5029                 // var chart = $(state.element_chart);
5030
5031                 var value = data.result[0];
5032                 var max = self.data('gauge-max-value') || null;
5033                 var adjust = self.data('gauge-adjust') || null;
5034                 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5035                 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5036                 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5037                 var stopColor = self.data('gauge-stop-color') || void 0;
5038                 var generateGradient = self.data('gauge-generate-gradient') || false;
5039
5040                 if(max === null) {
5041                         max = data.max;
5042                         state.gaugeMax = null;
5043                 }
5044                 else
5045                         state.gaugeMax = max;
5046
5047                 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5048                 //switch(adjust) {
5049                 //      case 'width': width = height * ratio; break;
5050                 //      case 'height':
5051                 //      default: height = width / ratio; break;
5052                 //}
5053                 //state.element.style.width = width.toString() + 'px';
5054                 //state.element.style.height = height.toString() + 'px';
5055
5056                 var lum_d = 0.05;
5057
5058                 var options = {
5059                         lines: 12,                                      // The number of lines to draw
5060                         angle: 0.15,                            // The length of each line
5061                         lineWidth: 0.44,                        // 0.44 The line thickness
5062                         pointer: {
5063                                 length: 0.8,                    // 0.9 The radius of the inner circle
5064                                 strokeWidth: 0.035,             // The rotation offset
5065                                 color: pointerColor             // Fill color
5066                         },
5067                         colorStart: startColor,         // Colors
5068                         colorStop: stopColor,           // just experiment with them
5069                         strokeColor: strokeColor,       // to see which ones work best for you
5070                         limitMax: true,
5071                         generateGradient: (generateGradient === true)?true:false,
5072                         gradientType: 0
5073                 };
5074
5075                 if (generateGradient.constructor === Array) {
5076                         // example options:
5077                         // data-gauge-generate-gradient="[0, 50, 100]"
5078                         // data-gauge-gradient-percent-color-0="#FFFFFF"
5079                         // data-gauge-gradient-percent-color-50="#999900"
5080                         // data-gauge-gradient-percent-color-100="#000000"
5081
5082                         options.percentColors = new Array();
5083                         var len = generateGradient.length;
5084                         while(len--) {
5085                                 var pcent = generateGradient[len];
5086                                 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5087                                 if(color !== false) {
5088                                         var a = new Array();
5089                                         a[0] = pcent / 100;
5090                                         a[1] = color;
5091                                         options.percentColors.unshift(a);
5092                                 }
5093                         }
5094                         if(options.percentColors.length === 0)
5095                                 delete options.percentColors;
5096                 }
5097                 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5098                         options.percentColors = [
5099                                 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5100                                 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5101                                 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5102                                 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5103                                 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5104                                 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5105                                 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5106                                 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5107                                 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5108                                 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5109                                 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5110                 }
5111
5112                 state.gauge_canvas = document.createElement('canvas');
5113                 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5114                 state.gauge_canvas.className = 'gaugeChart';
5115                 state.gauge_canvas.width  = width;
5116                 state.gauge_canvas.height = height;
5117                 state.element_chart.appendChild(state.gauge_canvas);
5118
5119                 var valuefontsize = Math.floor(height / 6);
5120                 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5121                 state.gaugeChartLabel = document.createElement('span');
5122                 state.gaugeChartLabel.className = 'gaugeChartLabel';
5123                 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5124                 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5125                 state.element_chart.appendChild(state.gaugeChartLabel);
5126
5127                 var titlefontsize = Math.round(valuefontsize / 2);
5128                 var titletop = 0;
5129                 state.gaugeChartTitle = document.createElement('span');
5130                 state.gaugeChartTitle.className = 'gaugeChartTitle';
5131                 state.gaugeChartTitle.innerHTML = state.title;
5132                 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5133                 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5134                 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5135                 state.element_chart.appendChild(state.gaugeChartTitle);
5136
5137                 var unitfontsize = Math.round(titlefontsize * 0.9);
5138                 state.gaugeChartUnits = document.createElement('span');
5139                 state.gaugeChartUnits.className = 'gaugeChartUnits';
5140                 state.gaugeChartUnits.innerHTML = state.units;
5141                 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5142                 state.element_chart.appendChild(state.gaugeChartUnits);
5143
5144                 state.gaugeChartMin = document.createElement('span');
5145                 state.gaugeChartMin.className = 'gaugeChartMin';
5146                 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5147                 state.element_chart.appendChild(state.gaugeChartMin);
5148
5149                 state.gaugeChartMax = document.createElement('span');
5150                 state.gaugeChartMax.className = 'gaugeChartMax';
5151                 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5152                 state.element_chart.appendChild(state.gaugeChartMax);
5153
5154                 // when we just re-create the chart
5155                 // do not animate the first update
5156                 var animate = true;
5157                 if(typeof state.gauge_instance !== 'undefined')
5158                         animate = false;
5159
5160                 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5161
5162                 state.___gaugeOld__ = {
5163                         value: value,
5164                         min: 0,
5165                         max: max,
5166                         valueLabel: null,
5167                         minLabel: null,
5168                         maxLabel: null
5169                 };
5170
5171                 // we will always feed a percentage
5172                 state.gauge_instance.minValue = 0;
5173                 state.gauge_instance.maxValue = 100;
5174
5175                 NETDATA.gaugeAnimation(state, animate);
5176                 NETDATA.gaugeSet(state, value, 0, max);
5177                 NETDATA.gaugeSetLabels(state, value, 0, max);
5178                 NETDATA.gaugeAnimation(state, true);
5179                 return true;
5180         };
5181
5182         // ----------------------------------------------------------------------------------------------------------------
5183         // Charts Libraries Registration
5184
5185         NETDATA.chartLibraries = {
5186                 "dygraph": {
5187                         initialize: NETDATA.dygraphInitialize,
5188                         create: NETDATA.dygraphChartCreate,
5189                         update: NETDATA.dygraphChartUpdate,
5190                         resize: function(state) {
5191                                 if(typeof state.dygraph_instance.resize === 'function')
5192                                         state.dygraph_instance.resize();
5193                         },
5194                         setSelection: NETDATA.dygraphSetSelection,
5195                         clearSelection:  NETDATA.dygraphClearSelection,
5196                         toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5197                         initialized: false,
5198                         enabled: true,
5199                         format: function(state) { return 'json'; },
5200                         options: function(state) { return 'ms|flip'; },
5201                         legend: function(state) {
5202                                 if(this.isSparkline(state) === false)
5203                                         return 'right-side';
5204                                 else
5205                                         return null;
5206                         },
5207                         autoresize: function(state) { return true; },
5208                         max_updates_to_recreate: function(state) { return 5000; },
5209                         track_colors: function(state) { return true; },
5210                         pixels_per_point: function(state) {
5211                                 if(this.isSparkline(state) === false)
5212                                         return 3;
5213                                 else
5214                                         return 2;
5215                         },
5216
5217                         isSparkline: function(state) {
5218                                 if(typeof state.dygraph_sparkline === 'undefined') {
5219                                         var t = $(state.element).data('dygraph-theme');
5220                                         if(t === 'sparkline')
5221                                                 state.dygraph_sparkline = true;
5222                                         else
5223                                                 state.dygraph_sparkline = false;
5224                                 }
5225                                 return state.dygraph_sparkline;
5226                         }
5227                 },
5228                 "sparkline": {
5229                         initialize: NETDATA.sparklineInitialize,
5230                         create: NETDATA.sparklineChartCreate,
5231                         update: NETDATA.sparklineChartUpdate,
5232                         resize: null,
5233                         setSelection: undefined, // function(state, t) { return true; },
5234                         clearSelection: undefined, // function(state) { return true; },
5235                         toolboxPanAndZoom: null,
5236                         initialized: false,
5237                         enabled: true,
5238                         format: function(state) { return 'array'; },
5239                         options: function(state) { return 'flip|abs'; },
5240                         legend: function(state) { return null; },
5241                         autoresize: function(state) { return false; },
5242                         max_updates_to_recreate: function(state) { return 5000; },
5243                         track_colors: function(state) { return false; },
5244                         pixels_per_point: function(state) { return 3; }
5245                 },
5246                 "peity": {
5247                         initialize: NETDATA.peityInitialize,
5248                         create: NETDATA.peityChartCreate,
5249                         update: NETDATA.peityChartUpdate,
5250                         resize: null,
5251                         setSelection: undefined, // function(state, t) { return true; },
5252                         clearSelection: undefined, // function(state) { return true; },
5253                         toolboxPanAndZoom: null,
5254                         initialized: false,
5255                         enabled: true,
5256                         format: function(state) { return 'ssvcomma'; },
5257                         options: function(state) { return 'null2zero|flip|abs'; },
5258                         legend: function(state) { return null; },
5259                         autoresize: function(state) { return false; },
5260                         max_updates_to_recreate: function(state) { return 5000; },
5261                         track_colors: function(state) { return false; },
5262                         pixels_per_point: function(state) { return 3; }
5263                 },
5264                 "morris": {
5265                         initialize: NETDATA.morrisInitialize,
5266                         create: NETDATA.morrisChartCreate,
5267                         update: NETDATA.morrisChartUpdate,
5268                         resize: null,
5269                         setSelection: undefined, // function(state, t) { return true; },
5270                         clearSelection: undefined, // function(state) { return true; },
5271                         toolboxPanAndZoom: null,
5272                         initialized: false,
5273                         enabled: true,
5274                         format: function(state) { return 'json'; },
5275                         options: function(state) { return 'objectrows|ms'; },
5276                         legend: function(state) { return null; },
5277                         autoresize: function(state) { return false; },
5278                         max_updates_to_recreate: function(state) { return 50; },
5279                         track_colors: function(state) { return false; },
5280                         pixels_per_point: function(state) { return 15; }
5281                 },
5282                 "google": {
5283                         initialize: NETDATA.googleInitialize,
5284                         create: NETDATA.googleChartCreate,
5285                         update: NETDATA.googleChartUpdate,
5286                         resize: null,
5287                         setSelection: undefined, //function(state, t) { return true; },
5288                         clearSelection: undefined, //function(state) { return true; },
5289                         toolboxPanAndZoom: null,
5290                         initialized: false,
5291                         enabled: true,
5292                         format: function(state) { return 'datatable'; },
5293                         options: function(state) { return ''; },
5294                         legend: function(state) { return null; },
5295                         autoresize: function(state) { return false; },
5296                         max_updates_to_recreate: function(state) { return 300; },
5297                         track_colors: function(state) { return false; },
5298                         pixels_per_point: function(state) { return 4; }
5299                 },
5300                 "raphael": {
5301                         initialize: NETDATA.raphaelInitialize,
5302                         create: NETDATA.raphaelChartCreate,
5303                         update: NETDATA.raphaelChartUpdate,
5304                         resize: null,
5305                         setSelection: undefined, // function(state, t) { return true; },
5306                         clearSelection: undefined, // function(state) { return true; },
5307                         toolboxPanAndZoom: null,
5308                         initialized: false,
5309                         enabled: true,
5310                         format: function(state) { return 'json'; },
5311                         options: function(state) { return ''; },
5312                         legend: function(state) { return null; },
5313                         autoresize: function(state) { return false; },
5314                         max_updates_to_recreate: function(state) { return 5000; },
5315                         track_colors: function(state) { return false; },
5316                         pixels_per_point: function(state) { return 3; }
5317                 },
5318                 "c3": {
5319                         initialize: NETDATA.c3Initialize,
5320                         create: NETDATA.c3ChartCreate,
5321                         update: NETDATA.c3ChartUpdate,
5322                         resize: null,
5323                         setSelection: undefined, // function(state, t) { return true; },
5324                         clearSelection: undefined, // function(state) { return true; },
5325                         toolboxPanAndZoom: null,
5326                         initialized: false,
5327                         enabled: true,
5328                         format: function(state) { return 'csvjsonarray'; },
5329                         options: function(state) { return 'milliseconds'; },
5330                         legend: function(state) { return null; },
5331                         autoresize: function(state) { return false; },
5332                         max_updates_to_recreate: function(state) { return 5000; },
5333                         track_colors: function(state) { return false; },
5334                         pixels_per_point: function(state) { return 15; }
5335                 },
5336                 "d3": {
5337                         initialize: NETDATA.d3Initialize,
5338                         create: NETDATA.d3ChartCreate,
5339                         update: NETDATA.d3ChartUpdate,
5340                         resize: null,
5341                         setSelection: undefined, // function(state, t) { return true; },
5342                         clearSelection: undefined, // function(state) { return true; },
5343                         toolboxPanAndZoom: null,
5344                         initialized: false,
5345                         enabled: true,
5346                         format: function(state) { return 'json'; },
5347                         options: function(state) { return ''; },
5348                         legend: function(state) { return null; },
5349                         autoresize: function(state) { return false; },
5350                         max_updates_to_recreate: function(state) { return 5000; },
5351                         track_colors: function(state) { return false; },
5352                         pixels_per_point: function(state) { return 3; }
5353                 },
5354                 "easypiechart": {
5355                         initialize: NETDATA.easypiechartInitialize,
5356                         create: NETDATA.easypiechartChartCreate,
5357                         update: NETDATA.easypiechartChartUpdate,
5358                         resize: null,
5359                         setSelection: NETDATA.easypiechartSetSelection,
5360                         clearSelection: NETDATA.easypiechartClearSelection,
5361                         toolboxPanAndZoom: null,
5362                         initialized: false,
5363                         enabled: true,
5364                         format: function(state) { return 'array'; },
5365                         options: function(state) { return 'absolute'; },
5366                         legend: function(state) { return null; },
5367                         autoresize: function(state) { return false; },
5368                         max_updates_to_recreate: function(state) { return 5000; },
5369                         track_colors: function(state) { return true; },
5370                         pixels_per_point: function(state) { return 3; },
5371                         aspect_ratio: 100
5372                 },
5373                 "gauge": {
5374                         initialize: NETDATA.gaugeInitialize,
5375                         create: NETDATA.gaugeChartCreate,
5376                         update: NETDATA.gaugeChartUpdate,
5377                         resize: null,
5378                         setSelection: NETDATA.gaugeSetSelection,
5379                         clearSelection: NETDATA.gaugeClearSelection,
5380                         toolboxPanAndZoom: null,
5381                         initialized: false,
5382                         enabled: true,
5383                         format: function(state) { return 'array'; },
5384                         options: function(state) { return 'absolute'; },
5385                         legend: function(state) { return null; },
5386                         autoresize: function(state) { return false; },
5387                         max_updates_to_recreate: function(state) { return 5000; },
5388                         track_colors: function(state) { return true; },
5389                         pixels_per_point: function(state) { return 3; },
5390                         aspect_ratio: 70
5391                 }
5392         };
5393
5394         NETDATA.registerChartLibrary = function(library, url) {
5395                 if(NETDATA.options.debug.libraries === true)
5396                         console.log("registering chart library: " + library);
5397
5398                 NETDATA.chartLibraries[library].url = url;
5399                 NETDATA.chartLibraries[library].initialized = true;
5400                 NETDATA.chartLibraries[library].enabled = true;
5401         };
5402
5403         // ----------------------------------------------------------------------------------------------------------------
5404         // Load required JS libraries and CSS
5405
5406         NETDATA.requiredJs = [
5407                 {
5408                         url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5409                         isAlreadyLoaded: function() {
5410                                 // check if bootstrap is loaded
5411                                 if(typeof $().emulateTransitionEnd == 'function')
5412                                         return true;
5413                                 else {
5414                                         if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5415                                                 return true;
5416                                         else
5417                                                 return false;
5418                                 }
5419                         }
5420                 },
5421                 {
5422                         url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5423                         isAlreadyLoaded: function() { return false; }
5424                 },
5425                 {
5426                         url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5427                         isAlreadyLoaded: function() { return false; }
5428                 }
5429         ];
5430
5431         NETDATA.requiredCSS = [
5432                 {
5433                         url: NETDATA.themes.current.bootstrap_css,
5434                         isAlreadyLoaded: function() {
5435                                 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5436                                         return true;
5437                                 else
5438                                         return false;
5439                         }
5440                 },
5441                 {
5442                         url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5443                         isAlreadyLoaded: function() { return false; }
5444                 },
5445                 {
5446                         url: NETDATA.themes.current.dashboard_css,
5447                         isAlreadyLoaded: function() { return false; }
5448                 },
5449                 {
5450                         url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5451                         isAlreadyLoaded: function() { return false; }
5452                 }
5453         ];
5454
5455         NETDATA.loadRequiredJs = function(index, callback) {
5456                 if(index >= NETDATA.requiredJs.length)  {
5457                         if(typeof callback === 'function')
5458                                 callback();
5459                         return;
5460                 }
5461
5462                 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5463                         NETDATA.loadRequiredJs(++index, callback);
5464                         return;
5465                 }
5466
5467                 if(NETDATA.options.debug.main_loop === true)
5468                         console.log('loading ' + NETDATA.requiredJs[index].url);
5469
5470                 $.ajax({
5471                         url: NETDATA.requiredJs[index].url,
5472                         cache: true,
5473                         dataType: "script"
5474                 })
5475                 .success(function() {
5476                         if(NETDATA.options.debug.main_loop === true)
5477                                 console.log('loaded ' + NETDATA.requiredJs[index].url);
5478
5479                         NETDATA.loadRequiredJs(++index, callback);
5480                 })
5481                 .fail(function() {
5482                         alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5483                 })
5484         };
5485
5486         NETDATA.loadRequiredCSS = function(index) {
5487                 if(index >= NETDATA.requiredCSS.length)
5488                         return;
5489
5490                 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5491                         NETDATA.loadRequiredCSS(++index);
5492                         return;
5493                 }
5494
5495                 if(NETDATA.options.debug.main_loop === true)
5496                         console.log('loading ' + NETDATA.requiredCSS[index].url);
5497
5498                 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5499                 NETDATA.loadRequiredCSS(++index);
5500         };
5501
5502
5503         // ----------------------------------------------------------------------------------------------------------------
5504         // Registry of netdata hosts
5505
5506         NETDATA.registry = {
5507                 server: null,           // the netdata registry server
5508                 person_guid: null,      // the unique ID of this browser / user
5509                 machine_guid: null,     // the unique ID the netdata server that served dashboard.js
5510                 hostname: null,         // the hostname of the netdata server that served dashboard.js
5511                 machines: null,                 // the user's other URLs
5512                 machines_array: null,   // the user's other URLs in an array
5513                 person_urls: null,
5514
5515                 parsePersonUrls: function(person_urls) {
5516                         // console.log(person_urls);
5517                         NETDATA.registry.person_urls = person_urls;
5518
5519                         if(person_urls) {
5520                                 NETDATA.registry.machines = {};
5521                                 NETDATA.registry.machines_array = new Array();
5522
5523                                 var now = new Date().getTime();
5524                                 var apu = person_urls;
5525                                 var i = apu.length;
5526                                 while(i--) {
5527                                         if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
5528                                                 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5529
5530                                                 var obj = {
5531                                                         guid: apu[i][0],
5532                                                         url: apu[i][1],
5533                                                         last_t: apu[i][2],
5534                                                         accesses: apu[i][3],
5535                                                         name: apu[i][4],
5536                                                         alternate_urls: new Array()
5537                                                 };
5538                                                 obj.alternate_urls.push(apu[i][1]);
5539
5540                                                 NETDATA.registry.machines[apu[i][0]] = obj;
5541                                                 NETDATA.registry.machines_array.push(obj);
5542                                         }
5543                                         else {
5544                                                 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
5545
5546                                                 var pu = NETDATA.registry.machines[apu[i][0]];
5547                                                 if(pu.last_t < apu[i][2]) {
5548                                                         pu.url = apu[i][1];
5549                                                         pu.last_t = apu[i][2];
5550                                                         pu.name = apu[i][4];
5551                                                 }
5552                                                 pu.accesses += apu[i][3];
5553                                                 pu.alternate_urls.push(apu[i][1]);
5554                                         }
5555                                 }
5556                         }
5557
5558                         if(typeof netdataRegistryCallback === 'function')
5559                                 netdataRegistryCallback(NETDATA.registry.machines_array);
5560                 },
5561
5562                 init: function() {
5563                         if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry)
5564                                 return;
5565
5566                         NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
5567                                 if(data) {
5568                                         NETDATA.registry.server = data.registry;
5569                                         NETDATA.registry.machine_guid = data.machine_guid;
5570                                         NETDATA.registry.hostname = data.hostname;
5571
5572                                         NETDATA.registry.access(10, function (person_urls) {
5573                                                 NETDATA.registry.parsePersonUrls(person_urls);
5574
5575                                         });
5576                                 }
5577                         });
5578                 },
5579
5580                 hello: function(host, callback) {
5581                         // send HELLO to a netdata server:
5582                         // 1. verifies the server is reachable
5583                         // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
5584                         $.ajax({
5585                                         url: host + '/api/v1/registry?action=hello',
5586                                         async: true,
5587                                         cache: false,
5588                                         xhrFields: { withCredentials: true } // required for the cookie
5589                                 })
5590                                 .done(function(data) {
5591                                         if(typeof data.status !== 'string' || data.status !== 'ok') {
5592                                                 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
5593                                                 data = null;
5594                                         }
5595
5596                                         if(typeof callback === 'function')
5597                                                 callback(data);
5598                                 })
5599                                 .fail(function() {
5600                                         NETDATA.error(407, host);
5601
5602                                         if(typeof callback === 'function')
5603                                                 callback(null);
5604                                 });
5605                 },
5606
5607                 access: function(max_redirects, callback) {
5608                         // send ACCESS to a netdata registry:
5609                         // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
5610                         // 2. it responds with a list of netdata servers we know
5611                         // the registry identifies us using a cookie it sets the first time we access it
5612                         // the registry may respond with a redirect URL to send us to another registry
5613                         $.ajax({
5614                                         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),
5615                                         async: true,
5616                                         cache: false,
5617                                         xhrFields: { withCredentials: true } // required for the cookie
5618                                 })
5619                                 .done(function(data) {
5620                                         var redirect = null;
5621                                         if(typeof data.registry === 'string')
5622                                                 redirect = data.registry;
5623
5624                                         if(typeof data.status !== 'string' || data.status !== 'ok') {
5625                                                 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5626                                                 data = null;
5627                                         }
5628
5629                                         if(data === null && redirect !== null && max_redirects > 0) {
5630                                                 NETDATA.registry.server = redirect;
5631                                                 NETDATA.registry.access(max_redirects - 1, callback);
5632                                         }
5633                                         else {
5634                                                 if(typeof data.person_guid === 'string')
5635                                                         NETDATA.registry.person_guid = data.person_guid;
5636
5637                                                 if(typeof callback === 'function')
5638                                                         callback(data.urls);
5639                                         }
5640                                 })
5641                                 .fail(function() {
5642                                         NETDATA.error(410, NETDATA.registry.server);
5643
5644                                         if(typeof callback === 'function')
5645                                                 callback(null);
5646                                 });
5647                 },
5648
5649                 delete: function(delete_url, callback) {
5650                         // send DELETE to a netdata registry:
5651                         $.ajax({
5652                                 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),
5653                                 async: true,
5654                                 cache: false,
5655                                 xhrFields: { withCredentials: true } // required for the cookie
5656                         })
5657                                 .done(function(data) {
5658                                         if(typeof data.status !== 'string' || data.status !== 'ok') {
5659                                                 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5660                                                 data = null;
5661                                         }
5662
5663                                         if(typeof callback === 'function')
5664                                                 callback(data);
5665                                 })
5666                                 .fail(function() {
5667                                         NETDATA.error(412, NETDATA.registry.server);
5668
5669                                         if(typeof callback === 'function')
5670                                                 callback(null);
5671                                 });
5672                 },
5673                 
5674                 switch: function(new_person_guid, callback) {
5675                         // impersonate
5676                         $.ajax({
5677                                 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,
5678                                 async: true,
5679                                 cache: false,
5680                                 xhrFields: { withCredentials: true } // required for the cookie
5681                         })
5682                                 .done(function(data) {
5683                                         if(typeof data.status !== 'string' || data.status !== 'ok') {
5684                                                 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
5685                                                 data = null;
5686                                         }
5687
5688                                         if(typeof callback === 'function')
5689                                                 callback(data);
5690                                 })
5691                                 .fail(function() {
5692                                         NETDATA.error(414, NETDATA.registry.server);
5693
5694                                         if(typeof callback === 'function')
5695                                                 callback(null);
5696                                 });
5697                 }
5698         };
5699
5700         // ----------------------------------------------------------------------------------------------------------------
5701         // Boot it!
5702
5703         NETDATA.errorReset();
5704         NETDATA.loadRequiredCSS(0);
5705
5706         NETDATA._loadjQuery(function() {
5707                 NETDATA.loadRequiredJs(0, function() {
5708                         if(typeof $().emulateTransitionEnd !== 'function') {
5709                                 // bootstrap is not available
5710                                 NETDATA.options.current.show_help = false;
5711                         }
5712
5713                         if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
5714                                 if(NETDATA.options.debug.main_loop === true)
5715                                         console.log('starting chart refresh thread');
5716
5717                                 NETDATA.start();
5718                         }
5719                 });
5720         });
5721
5722         // window.NETDATA = NETDATA;
5723 // })(window, document);