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