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