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