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