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