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