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