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