]> arthur.barton.de Git - netdata.git/blob - web/dashboard.js
remapped the speeds for ALT, SHIFT, CONTROL
[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 === parent.unselected)
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 viariables
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                         this.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                                 this.log(logme + 'flipped parameters, rejecting it.');
1780                                 return false;
1781                         }
1782
1783                         if(typeof this.fixed_min_duration === 'undefined')
1784                                 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1785
1786                         var min_duration = this.fixed_min_duration;
1787                         var current_duration = Math.round(this.view_before - this.view_after);
1788
1789                         // round the numbers
1790                         after = Math.round(after);
1791                         before = Math.round(before);
1792
1793                         // align them to update_every
1794                         // stretching them further away
1795                         after -= after % this.data_update_every;
1796                         before += this.data_update_every - (before % this.data_update_every);
1797
1798                         // the final wanted duration
1799                         var wanted_duration = before - after;
1800
1801                         // to allow panning, accept just a point below our minimum
1802                         if((current_duration - this.data_update_every) < min_duration)
1803                                 min_duration = current_duration - this.data_update_every;
1804
1805                         // we do it, but we adjust to minimum size and return false
1806                         // when the wanted size is below the current and the minimum
1807                         // and we zoom
1808                         if(wanted_duration < current_duration && wanted_duration < min_duration) {
1809                                 if(this.debug === true)
1810                                         this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1811
1812                                 min_duration = this.fixed_min_duration;
1813
1814                                 var dt = (min_duration - wanted_duration) / 2;
1815                                 before += dt;
1816                                 after -= dt;
1817                                 wanted_duration = before - after;
1818                                 ret = false;
1819                         }
1820
1821                         var tolerance = this.data_update_every * 2;
1822                         var movement = Math.abs(before - this.view_before);
1823
1824                         if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
1825                                 if(this.debug === true)
1826                                         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);
1827                                 return false;
1828                         }
1829
1830                         if(this.current.name === 'auto') {
1831                                 this.log(logme + 'caller called me with mode: ' + this.current.name);
1832                                 this.setMode('pan');
1833                         }
1834
1835                         if(this.debug === true)
1836                                 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);
1837
1838                         this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1839                         this.current.force_after_ms = after;
1840                         this.current.force_before_ms = before;
1841                         NETDATA.globalPanAndZoom.setMaster(this, after, before);
1842                         return ret;
1843                 }
1844
1845                 this.legendFormatValue = function(value) {
1846                         if(value === null || value === 'undefined') return '-';
1847                         if(typeof value !== 'number') return value;
1848
1849                         var abs = Math.abs(value);
1850                         if(abs >= 1000) return (Math.round(value)).toLocaleString();
1851                         if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1852                         if(abs >= 1   ) return (Math.round(value * 100) / 100).toLocaleString();
1853                         if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1854                         return (Math.round(value * 10000) / 10000).toLocaleString();
1855                 }
1856
1857                 this.legendSetLabelValue = function(label, value) {
1858                         var series = this.element_legend_childs.series[label];
1859                         if(typeof series === 'undefined') return;
1860                         if(series.value === null && series.user === null) return;
1861
1862                         // if the value has not changed, skip DOM update
1863                         //if(series.last === value) return;
1864
1865                         var s, r;
1866                         if(typeof value === 'number') {
1867                                 var v = Math.abs(value);
1868                                 s = r = this.legendFormatValue(value);
1869
1870                                 if(typeof series.last === 'number') {
1871                                         if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1872                                         else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1873                                         else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1874                                 }
1875                                 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1876                                 series.last = v;
1877                         }
1878                         else {
1879                                 s = r = value;
1880                                 series.last = value;
1881                         }
1882
1883                         if(series.value !== null) series.value.innerHTML = s;
1884                         if(series.user !== null) series.user.innerHTML = r;
1885                 }
1886
1887                 this.legendSetDate = function(ms) {
1888                         if(typeof ms !== 'number') {
1889                                 this.legendShowUndefined();
1890                                 return;
1891                         }
1892
1893                         var d = new Date(ms);
1894
1895                         if(this.element_legend_childs.title_date)
1896                                 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
1897
1898                         if(this.element_legend_childs.title_time)
1899                                 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
1900
1901                         if(this.element_legend_childs.title_units)
1902                                 this.element_legend_childs.title_units.innerHTML = this.units;
1903                 }
1904
1905                 this.legendShowUndefined = function() {
1906                         if(this.element_legend_childs.title_date)
1907                                 this.element_legend_childs.title_date.innerHTML = '&nbsp;';
1908
1909                         if(this.element_legend_childs.title_time)
1910                                 this.element_legend_childs.title_time.innerHTML = this.chart.name;
1911
1912                         if(this.element_legend_childs.title_units)
1913                                 this.element_legend_childs.title_units.innerHTML = '&nbsp;';
1914
1915                         if(this.data && this.element_legend_childs.series !== null) {
1916                                 var labels = this.data.dimension_names;
1917                                 var i = labels.length;
1918                                 while(i--) {
1919                                         var label = labels[i];
1920
1921                                         if(typeof label === 'undefined') continue;
1922                                         if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1923                                         this.legendSetLabelValue(label, null);
1924                                 }
1925                         }
1926                 }
1927
1928                 this.legendShowLatestValues = function() {
1929                         if(this.chart === null) return;
1930                         if(this.selected) return;
1931
1932                         if(this.data === null || this.element_legend_childs.series === null) {
1933                                 this.legendShowUndefined();
1934                                 return;
1935                         }
1936
1937                         var show_undefined = true;
1938                         if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
1939                                 show_undefined = false;
1940
1941                         if(show_undefined) {
1942                                 this.legendShowUndefined();
1943                                 return;
1944                         }
1945
1946                         this.legendSetDate(this.view_before);
1947
1948                         var labels = this.data.dimension_names;
1949                         var i = labels.length;
1950                         while(i--) {
1951                                 var label = labels[i];
1952
1953                                 if(typeof label === 'undefined') continue;
1954                                 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1955
1956                                 if(show_undefined)
1957                                         this.legendSetLabelValue(label, null);
1958                                 else
1959                                         this.legendSetLabelValue(label, this.data.view_latest_values[i]);
1960                         }
1961                 }
1962
1963                 this.legendReset = function() {
1964                         this.legendShowLatestValues();
1965                 }
1966
1967                 // this should be called just ONCE per dimension per chart
1968                 this._chartDimensionColor = function(label) {
1969                         if(this.colors === null) this.chartColors();
1970
1971                         if(typeof this.colors_assigned[label] === 'undefined') {
1972                                 if(this.colors_available.length === 0) {
1973                                         for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
1974                                                 this.colors_available.push(NETDATA.themes.current.colors[i]);
1975                                 }
1976
1977                                 this.colors_assigned[label] = this.colors_available.shift();
1978
1979                                 if(this.debug === true)
1980                                         this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
1981                         }
1982                         else {
1983                                 if(this.debug === true)
1984                                         this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
1985                         }
1986
1987                         this.colors.push(this.colors_assigned[label]);
1988                         return this.colors_assigned[label];
1989                 }
1990
1991                 this.chartColors = function() {
1992                         if(this.colors !== null) return this.colors;
1993
1994                         this.colors = new Array();
1995                         this.colors_available = new Array();
1996
1997                         var c = $(this.element).data('colors');
1998                         // this.log('read colors: ' + c);
1999                         if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2000                                 if(typeof c !== 'string') {
2001                                         this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2002                                 }
2003                                 else {
2004                                         c = c.split(' ');
2005                                         var added = 0;
2006
2007                                         while(added < 20) {
2008                                                 for(var i = 0, len = c.length; i < len ; i++) {
2009                                                         added++;
2010                                                         this.colors_available.push(c[i]);
2011                                                         // this.log('adding color: ' + c[i]);
2012                                                 }
2013                                         }
2014                                 }
2015                         }
2016
2017                         // push all the standard colors too
2018                         for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2019                                 this.colors_available.push(NETDATA.themes.current.colors[i]);
2020
2021                         return this.colors;
2022                 }
2023
2024                 this.legendUpdateDOM = function() {
2025                         var needed = false;
2026
2027                         // check that the legend DOM is up to date for the downloaded dimensions
2028                         if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2029                                 // this.log('the legend does not have any series - requesting legend update');
2030                                 needed = true;
2031                         }
2032                         else if(this.data === null) {
2033                                 // this.log('the chart does not have any data - requesting legend update');
2034                                 needed = true;
2035                         }
2036                         else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2037                                 needed = true;
2038                         }
2039                         else {
2040                                 var labels = this.data.dimension_names.toString();
2041                                 if(labels !== this.element_legend_childs.series.labels_key) {
2042                                         needed = true;
2043
2044                                         if(this.debug === true)
2045                                                 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2046                                 }
2047                         }
2048
2049                         if(needed === false) {
2050                                 // make sure colors available
2051                                 this.chartColors();
2052
2053                                 // do we have to update the current values?
2054                                 // we do this, only when the visible chart is current
2055                                 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2056                                         if(this.debug === true)
2057                                                 this.log('chart is in latest position... updating values on legend...');
2058
2059                                         //var labels = this.data.dimension_names;
2060                                         //var i = labels.length;
2061                                         //while(i--)
2062                                         //      this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2063                                 }
2064                                 return;
2065                         }
2066                         if(this.colors === null) {
2067                                 // this is the first time we update the chart
2068                                 // let's assign colors to all dimensions
2069                                 if(this.library.track_colors() === true)
2070                                         for(var dim in this.chart.dimensions)
2071                                                 this._chartDimensionColor(this.chart.dimensions[dim].name);
2072                         }
2073                         // we will re-generate the colors for the chart
2074                         // based on the selected dimensions
2075                         this.colors = null;
2076
2077                         if(this.debug === true)
2078                                 this.log('updating Legend DOM');
2079
2080                         // mark all dimensions as invalid
2081                         this.dimensions_visibility.invalidateAll();
2082
2083                         var genLabel = function(state, parent, name, count) {
2084                                 var color = state._chartDimensionColor(name);
2085
2086                                 var user_element = null;
2087                                 var user_id = self.data('show-value-of-' + name + '-at') || null;
2088                                 if(user_id !== null) {
2089                                         user_element = document.getElementById(user_id) || null;
2090                                         if(user_element === null)
2091                                                 me.log('Cannot find element with id: ' + user_id);
2092                                 }
2093
2094                                 state.element_legend_childs.series[name] = {
2095                                         name: document.createElement('span'),
2096                                         value: document.createElement('span'),
2097                                         user: user_element,
2098                                         last: null
2099                                 };
2100
2101                                 var label = state.element_legend_childs.series[name];
2102
2103                                 // create the dimension visibility tracking for this label
2104                                 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2105
2106                                 var rgb = NETDATA.colorHex2Rgb(color);
2107                                 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2108                                         + state.chart.chart_type
2109                                         + '" style="background-color: '
2110                                         + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2111                                         + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2112
2113                                 var text = document.createTextNode(' ' + name);
2114                                 label.name.appendChild(text);
2115
2116                                 if(count > 0)
2117                                         parent.appendChild(document.createElement('br'));
2118
2119                                 parent.appendChild(label.name);
2120                                 parent.appendChild(label.value);
2121                         };
2122
2123                         var content = document.createElement('div');
2124
2125                         if(this.hasLegend()) {
2126                                 this.element_legend_childs = {
2127                                         content: content,
2128                                         resize_handler: document.createElement('div'),
2129                                         toolbox: document.createElement('div'),
2130                                         toolbox_left: document.createElement('div'),
2131                                         toolbox_right: document.createElement('div'),
2132                                         toolbox_reset: document.createElement('div'),
2133                                         toolbox_zoomin: document.createElement('div'),
2134                                         toolbox_zoomout: document.createElement('div'),
2135                                         toolbox_volume: document.createElement('div'),
2136                                         title_date: document.createElement('span'),
2137                                         title_time: document.createElement('span'),
2138                                         title_units: document.createElement('span'),
2139                                         nano: document.createElement('div'),
2140                                         nano_options: {
2141                                                 paneClass: 'netdata-legend-series-pane',
2142                                                 sliderClass: 'netdata-legend-series-slider',
2143                                                 contentClass: 'netdata-legend-series-content',
2144                                                 enabledClass: '__enabled',
2145                                                 flashedClass: '__flashed',
2146                                                 activeClass: '__active',
2147                                                 tabIndex: -1,
2148                                                 alwaysVisible: true,
2149                                                 sliderMinHeight: 10
2150                                         },
2151                                         series: {}
2152                                 };
2153
2154                                 this.element_legend.innerHTML = '';
2155
2156                                 if(this.library.toolboxPanAndZoom !== null) {
2157
2158                                         function get_pan_and_zoom_step(event) {
2159                                                 if (event.shiftKey)
2160                                                         return NETDATA.options.current.pan_and_zoom_step * NETDATA.options.current.pan_and_zoom_step_multiplier_shift;
2161                                                 else if (event.altKey)
2162                                                         return NETDATA.options.current.pan_and_zoom_step * NETDATA.options.current.pan_and_zoom_step_multiplier_alt;
2163                                                 else if (event.ctrlKey)
2164                                                         return NETDATA.options.current.pan_and_zoom_step * NETDATA.options.current.pan_and_zoom_step_multiplier_control;
2165                                                 else
2166                                                         return NETDATA.options.current.pan_and_zoom_step;
2167                                         }
2168
2169                                         this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2170                                         this.element.appendChild(this.element_legend_childs.toolbox);
2171
2172                                         this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2173                                         this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2174                                         this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2175                                         this.element_legend_childs.toolbox_left.onclick = function(e) {
2176                                                 e.preventDefault();
2177
2178                                                 var dt = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2179                                                 var before = that.view_before - dt;
2180                                                 var after = that.view_after - dt;
2181                                                 if(after >= that.netdata_first)
2182                                                         that.library.toolboxPanAndZoom(that, after, before);
2183                                         }
2184                                         if(NETDATA.options.current.show_help === true)
2185                                                 $(this.element_legend_childs.toolbox_left).popover({
2186                                                 container: "body",
2187                                                 animation: false,
2188                                                 html: true,
2189                                                 trigger: 'hover',
2190                                                 placement: 'bottom',
2191                                                 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2192                                                 title: 'Pan Left',
2193                                                 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>'
2194                                         });
2195
2196
2197                                         this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2198                                         this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2199                                         this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2200                                         this.element_legend_childs.toolbox_reset.onclick = function(e) {
2201                                                 e.preventDefault();
2202                                                 NETDATA.resetAllCharts(that);
2203                                         }
2204                                         if(NETDATA.options.current.show_help === true)
2205                                                 $(this.element_legend_childs.toolbox_reset).popover({
2206                                                 container: "body",
2207                                                 animation: false,
2208                                                 html: true,
2209                                                 trigger: 'hover',
2210                                                 placement: 'bottom',
2211                                                 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2212                                                 title: 'Chart Reset',
2213                                                 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>'
2214                                         });
2215                                         
2216                                         this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2217                                         this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2218                                         this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2219                                         this.element_legend_childs.toolbox_right.onclick = function(e) {
2220                                                 e.preventDefault();
2221                                                 var dt = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2222                                                 var before = that.view_before + dt;
2223                                                 var after = that.view_after + dt;
2224                                                 if(before <= that.netdata_last)
2225                                                         that.library.toolboxPanAndZoom(that, after, before);
2226                                         }
2227                                         if(NETDATA.options.current.show_help === true)
2228                                                 $(this.element_legend_childs.toolbox_right).popover({
2229                                                 container: "body",
2230                                                 animation: false,
2231                                                 html: true,
2232                                                 trigger: 'hover',
2233                                                 placement: 'bottom',
2234                                                 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2235                                                 title: 'Pan Right',
2236                                                 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>'
2237                                         });
2238
2239                                         
2240                                         this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2241                                         this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2242                                         this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2243                                         this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2244                                                 e.preventDefault();
2245                                                 var dt = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2246                                                 var before = that.view_before - dt;
2247                                                 var after = that.view_after + dt;
2248                                                 that.library.toolboxPanAndZoom(that, after, before);
2249                                         }
2250                                         if(NETDATA.options.current.show_help === true)
2251                                                 $(this.element_legend_childs.toolbox_zoomin).popover({
2252                                                 container: "body",
2253                                                 animation: false,
2254                                                 html: true,
2255                                                 trigger: 'hover',
2256                                                 placement: 'bottom',
2257                                                 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2258                                                 title: 'Chart Zoom In',
2259                                                 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>'
2260                                         });
2261                                         
2262                                         this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2263                                         this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2264                                         this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2265                                         this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2266                                                 e.preventDefault();
2267                                                 var dt = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2268                                                 var before = that.view_before + dt;
2269                                                 var after = that.view_after - dt;
2270
2271                                                 that.library.toolboxPanAndZoom(that, after, before);
2272                                         }
2273                                         if(NETDATA.options.current.show_help === true)
2274                                                 $(this.element_legend_childs.toolbox_zoomout).popover({
2275                                                 container: "body",
2276                                                 animation: false,
2277                                                 html: true,
2278                                                 trigger: 'hover',
2279                                                 placement: 'bottom',
2280                                                 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2281                                                 title: 'Chart Zoom Out',
2282                                                 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>'
2283                                         });
2284                                         
2285                                         //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2286                                         //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2287                                         //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2288                                         //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2289                                         //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2290                                                 //e.preventDefault();
2291                                                 //alert('clicked toolbox_volume on ' + that.id);
2292                                         //}
2293                                 }
2294                                 else {
2295                                         this.element_legend_childs.toolbox = null;
2296                                         this.element_legend_childs.toolbox_left = null;
2297                                         this.element_legend_childs.toolbox_reset = null;
2298                                         this.element_legend_childs.toolbox_right = null;
2299                                         this.element_legend_childs.toolbox_zoomin = null;
2300                                         this.element_legend_childs.toolbox_zoomout = null;
2301                                         this.element_legend_childs.toolbox_volume = null;
2302                                 }
2303                                 
2304                                 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2305                                 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2306                                 this.element.appendChild(this.element_legend_childs.resize_handler);
2307                                 if(NETDATA.options.current.show_help === true)
2308                                         $(this.element_legend_childs.resize_handler).popover({
2309                                         container: "body",
2310                                         animation: false,
2311                                         html: true,
2312                                         trigger: 'hover',
2313                                         placement: 'bottom',
2314                                         delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2315                                         title: 'Chart Resize',
2316                                         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>'
2317                                 });
2318
2319                                 // mousedown event
2320                                 this.element_legend_childs.resize_handler.onmousedown =
2321                                         function(e) {
2322                                                 that.resizeHandler(e);
2323                                         };
2324
2325                                 // touchstart event
2326                                 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2327                                         that.resizeHandler(e);
2328                                 }, false);
2329
2330                                 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2331                                 this.element_legend.appendChild(this.element_legend_childs.title_date);
2332
2333                                 this.element_legend.appendChild(document.createElement('br'));
2334
2335                                 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2336                                 this.element_legend.appendChild(this.element_legend_childs.title_time);
2337
2338                                 this.element_legend.appendChild(document.createElement('br'));
2339
2340                                 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2341                                 this.element_legend.appendChild(this.element_legend_childs.title_units);
2342
2343                                 this.element_legend.appendChild(document.createElement('br'));
2344
2345                                 this.element_legend_childs.nano.className = 'netdata-legend-series';
2346                                 this.element_legend.appendChild(this.element_legend_childs.nano);
2347
2348                                 content.className = 'netdata-legend-series-content';
2349                                 this.element_legend_childs.nano.appendChild(content);
2350
2351                                 if(NETDATA.options.current.show_help === true)
2352                                         $(content).popover({
2353                                         container: "body",
2354                                         animation: false,
2355                                         html: true,
2356                                         trigger: 'hover',
2357                                         placement: 'bottom',
2358                                         title: 'Chart Legend',
2359                                         delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2360                                         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>'
2361                                 });
2362                         }
2363                         else {
2364                                 this.element_legend_childs = {
2365                                         content: content,
2366                                         resize_handler: null,
2367                                         toolbox: null,
2368                                         toolbox_left: null,
2369                                         toolbox_right: null,
2370                                         toolbox_reset: null,
2371                                         toolbox_zoomin: null,
2372                                         toolbox_zoomout: null,
2373                                         toolbox_volume: null,
2374                                         title_date: null,
2375                                         title_time: null,
2376                                         title_units: null,
2377                                         nano: null,
2378                                         nano_options: null,
2379                                         series: {}
2380                                 };
2381                         }
2382
2383                         if(this.data) {
2384                                 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2385                                 if(this.debug === true)
2386                                         this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2387
2388                                 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2389                                         genLabel(this, content, this.data.dimension_names[i], i);
2390                                 }
2391                         }
2392                         else {
2393                                 var tmp = new Array();
2394                                 for(var dim in this.chart.dimensions) {
2395                                         tmp.push(this.chart.dimensions[dim].name);
2396                                         genLabel(this, content, this.chart.dimensions[dim].name, i);
2397                                 }
2398                                 this.element_legend_childs.series.labels_key = tmp.toString();
2399                                 if(this.debug === true)
2400                                         this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2401                         }
2402
2403                         // create a hidden div to be used for hidding
2404                         // the original legend of the chart library
2405                         var el = document.createElement('div');
2406                         if(this.element_legend !== null)
2407                                 this.element_legend.appendChild(el);
2408                         el.style.display = 'none';
2409
2410                         this.element_legend_childs.hidden = document.createElement('div');
2411                         el.appendChild(this.element_legend_childs.hidden);
2412
2413                         if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2414                                 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2415
2416                         this.legendShowLatestValues();
2417                 }
2418
2419                 this.hasLegend = function() {
2420                         if(typeof this.___hasLegendCache___ !== 'undefined')
2421                                 return this.___hasLegendCache___;
2422
2423                         var leg = false;
2424                         if(this.library && this.library.legend(this) === 'right-side') {
2425                                 var legend = $(this.element).data('legend') || 'yes';
2426                                 if(legend === 'yes') leg = true;
2427                         }
2428
2429                         this.___hasLegendCache___ = leg;
2430                         return leg;
2431                 }
2432
2433                 this.legendWidth = function() {
2434                         return (this.hasLegend())?140:0;
2435                 }
2436
2437                 this.legendHeight = function() {
2438                         return $(this.element).height();
2439                 }
2440
2441                 this.chartWidth = function() {
2442                         return $(this.element).width() - this.legendWidth();
2443                 }
2444
2445                 this.chartHeight = function() {
2446                         return $(this.element).height();
2447                 }
2448
2449                 this.chartPixelsPerPoint = function() {
2450                         // force an options provided detail
2451                         var px = this.pixels_per_point;
2452
2453                         if(this.library && px < this.library.pixels_per_point(this))
2454                                 px = this.library.pixels_per_point(this);
2455
2456                         if(px < NETDATA.options.current.pixels_per_point)
2457                                 px = NETDATA.options.current.pixels_per_point;
2458
2459                         return px;
2460                 }
2461
2462                 this.needsRecreation = function() {
2463                         return (
2464                                         this.chart_created === true
2465                                         && this.library
2466                                         && this.library.autoresize() === false
2467                                         && this.tm.last_resized < NETDATA.options.last_resized
2468                                 );
2469                 }
2470
2471                 this.chartURL = function() {
2472                         var after, before, points_multiplier = 1;
2473                         if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2474                                 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2475
2476                                 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2477                                 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2478                                 this.view_after = after * 1000;
2479                                 this.view_before = before * 1000;
2480
2481                                 this.requested_padding = null;
2482                                 points_multiplier = 1;
2483                         }
2484                         else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2485                                 this.tm.pan_and_zoom_seq = 0;
2486
2487                                 before = Math.round(this.current.force_before_ms / 1000);
2488                                 after  = Math.round(this.current.force_after_ms / 1000);
2489                                 this.view_after = after * 1000;
2490                                 this.view_before = before * 1000;
2491
2492                                 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2493                                         this.requested_padding = Math.round((before - after) / 2);
2494                                         after -= this.requested_padding;
2495                                         before += this.requested_padding;
2496                                         this.requested_padding *= 1000;
2497                                         points_multiplier = 2;
2498                                 }
2499
2500                                 this.current.force_before_ms = null;
2501                                 this.current.force_after_ms = null;
2502                         }
2503                         else {
2504                                 this.tm.pan_and_zoom_seq = 0;
2505
2506                                 before = this.before;
2507                                 after  = this.after;
2508                                 this.view_after = after * 1000;
2509                                 this.view_before = before * 1000;
2510
2511                                 this.requested_padding = null;
2512                                 points_multiplier = 1;
2513                         }
2514
2515                         this.requested_after = after * 1000;
2516                         this.requested_before = before * 1000;
2517
2518                         this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2519
2520                         // build the data URL
2521                         this.data_url = this.host + this.chart.data_url;
2522                         this.data_url += "&format="  + this.library.format();
2523                         this.data_url += "&points="  + (this.data_points * points_multiplier).toString();
2524                         this.data_url += "&group="   + this.method;
2525                         this.data_url += "&options=" + this.library.options(this);
2526                         this.data_url += '|jsonwrap';
2527
2528                         if(NETDATA.options.current.eliminate_zero_dimensions === true)
2529                                 this.data_url += '|nonzero';
2530
2531                         if(this.append_options !== null)
2532                                 this.data_url += '|' + this.append_options.toString();
2533
2534                         if(after)
2535                                 this.data_url += "&after="  + after.toString();
2536
2537                         if(before)
2538                                 this.data_url += "&before=" + before.toString();
2539
2540                         if(this.dimensions)
2541                                 this.data_url += "&dimensions=" + this.dimensions;
2542
2543                         if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2544                                 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2545                 }
2546
2547                 this.redrawChart = function() {
2548                         if(this.data !== null)
2549                                 this.updateChartWithData(this.data);
2550                 }
2551
2552                 this.updateChartWithData = function(data) {
2553                         if(this.debug === true)
2554                                 this.log('updateChartWithData() called.');
2555
2556                         this._updating = false;
2557
2558                         // this may force the chart to be re-created
2559                         resizeChart();
2560
2561                         this.data = data;
2562                         this.updates_counter++;
2563                         this.updates_since_last_unhide++;
2564                         this.updates_since_last_creation++;
2565
2566                         var started = new Date().getTime();
2567
2568                         // if the result is JSON, find the latest update-every
2569                         this.data_update_every = data.view_update_every * 1000;
2570                         this.data_after = data.after * 1000;
2571                         this.data_before = data.before * 1000;
2572                         this.netdata_first = data.first_entry * 1000;
2573                         this.netdata_last = data.last_entry * 1000;
2574                         this.data_points = data.points;
2575                         data.state = this;
2576
2577                         if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2578                                 if(this.view_after < this.data_after) {
2579                                         // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2580                                         this.view_after = this.data_after;
2581                                 }
2582
2583                                 if(this.view_before > this.data_before) {
2584                                         // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2585                                         this.view_before = this.data_before;
2586                                 }
2587                         }
2588                         else {
2589                                 this.view_after = this.data_after;
2590                                 this.view_before = this.data_before;
2591                         }
2592
2593                         if(this.debug === true) {
2594                                 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2595
2596                                 if(this.current.force_after_ms)
2597                                         this.log('STATUS: forced    : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2598                                 else
2599                                         this.log('STATUS: forced    : unset');
2600
2601                                 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2602                                 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2603                                 this.log('STATUS: rendered  : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2604                                 this.log('STATUS: points    : ' + (this.data_points).toString());
2605                         }
2606
2607                         if(this.data_points === 0) {
2608                                 noDataToShow();
2609                                 return;
2610                         }
2611
2612                         if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2613                                 if(this.debug === true)
2614                                         this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2615
2616                                 this.chart_created = false;
2617                         }
2618
2619                         // check and update the legend
2620                         this.legendUpdateDOM();
2621
2622                         if(this.chart_created === true
2623                                 && typeof this.library.update === 'function') {
2624
2625                                 if(this.debug === true)
2626                                         this.log('updating chart...');
2627
2628                                 if(callChartLibraryUpdateSafely(data) === false)
2629                                         return;
2630                         }
2631                         else {
2632                                 if(this.debug === true)
2633                                         this.log('creating chart...');
2634
2635                                 if(callChartLibraryCreateSafely(data) === false)
2636                                         return;
2637                         }
2638                         hideMessage();
2639                         this.legendShowLatestValues();
2640                         if(this.selected === true)
2641                                 NETDATA.globalSelectionSync.stop();
2642
2643                         // update the performance counters
2644                         var now = new Date().getTime();
2645                         this.tm.last_updated = now;
2646
2647                         // don't update last_autorefreshed if this chart is
2648                         // forced to be updated with global PanAndZoom
2649                         if(NETDATA.globalPanAndZoom.isActive())
2650                                 this.tm.last_autorefreshed = 0;
2651                         else {
2652                                 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes)
2653                                         this.tm.last_autorefreshed = Math.round(now / this.data_update_every) * this.data_update_every;
2654                                 else
2655                                         this.tm.last_autorefreshed = now;
2656                         }
2657
2658                         this.refresh_dt_ms = now - started;
2659                         NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2660
2661                         if(this.refresh_dt_element !== null)
2662                                 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2663                 }
2664
2665                 this.updateChart = function(callback) {
2666                         if(this.debug === true)
2667                                 this.log('updateChart() called.');
2668
2669                         if(this._updating === true) {
2670                                 if(this.debug === true)
2671                                         this.log('I am already updating...');
2672
2673                                 if(typeof callback === 'function') callback();
2674                                 return false;
2675                         }
2676
2677                         // due to late initialization of charts and libraries
2678                         // we need to check this too
2679                         if(this.enabled === false) {
2680                                 if(this.debug === true)
2681                                         this.log('I am not enabled');
2682
2683                                 if(typeof callback === 'function') callback();
2684                                 return false;
2685                         }
2686
2687                         if(canBeRendered() === false) {
2688                                 if(typeof callback === 'function') callback();
2689                                 return false;
2690                         }
2691
2692                         if(this.chart === null) {
2693                                 this.getChart(function() { that.updateChart(callback); });
2694                                 return false;
2695                         }
2696
2697                         if(this.library.initialized === false) {
2698                                 if(this.library.enabled === true) {
2699                                         this.library.initialize(function() { that.updateChart(callback); });
2700                                         return false;
2701                                 }
2702                                 else {
2703                                         error('chart library "' + this.library_name + '" is not available.');
2704                                         if(typeof callback === 'function') callback();
2705                                         return false;
2706                                 }
2707                         }
2708
2709                         this.clearSelection();
2710                         this.chartURL();
2711
2712                         if(this.debug === true)
2713                                 this.log('updating from ' + this.data_url);
2714
2715                         this._updating = true;
2716
2717                         this.xhr = $.ajax( {
2718                                 url: this.data_url,
2719                                 crossDomain: NETDATA.options.crossDomainAjax,
2720                                 cache: false,
2721                                 async: true
2722                         })
2723                         .success(function(data) {
2724                                 if(that.debug === true)
2725                                         that.log('data received. updating chart.');
2726
2727                                 that.updateChartWithData(data);
2728                         })
2729                         .fail(function() {
2730                                 error('data download failed for url: ' + that.data_url);
2731                         })
2732                         .always(function() {
2733                                 this._updating = false;
2734                                 if(typeof callback === 'function') callback();
2735                         });
2736
2737                         return true;
2738                 }
2739
2740                 this.isVisible = function(nocache) {
2741                         if(typeof nocache === 'undefined')
2742                                 nocache = false;
2743
2744                         // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2745
2746                         // caching - we do not evaluate the charts visibility
2747                         // if the page has not been scrolled since the last check
2748                         if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2749                                 return this.___isVisible___;
2750
2751                         this.tm.last_visible_check = new Date().getTime();
2752
2753                         var wh = window.innerHeight;
2754                         var x = this.element.getBoundingClientRect();
2755                         var ret = 0;
2756                         var tolerance = 0;
2757
2758                         if(x.width === 0 || x.height === 0) {
2759                                 hideChart();
2760                                 this.___isVisible___ = false;
2761                                 return this.___isVisible___;
2762                         }
2763
2764                         if(x.top < 0 && -x.top > x.height) {
2765                                 // the chart is entirely above
2766                                 ret = -x.top - x.height;
2767                         }
2768                         else if(x.top > wh) {
2769                                 // the chart is entirely below
2770                                 ret = x.top - wh;
2771                         }
2772
2773                         if(ret > tolerance) {
2774                                 // the chart is too far
2775
2776                                 hideChart();
2777                                 this.___isVisible___ = false;
2778                                 return this.___isVisible___;
2779                         }
2780                         else {
2781                                 // the chart is inside or very close
2782
2783                                 unhideChart();
2784                                 this.___isVisible___ = true;
2785                                 return this.___isVisible___;
2786                         }
2787                 }
2788
2789                 this.isAutoRefreshed = function() {
2790                         return (this.current.autorefresh);
2791                 }
2792
2793                 this.canBeAutoRefreshed = function() {
2794                         now = new Date().getTime();
2795
2796                         if(this.enabled === false) {
2797                                 if(this.debug === true)
2798                                         this.log('I am not enabled');
2799
2800                                 return false;
2801                         }
2802
2803                         if(this.library === null || this.library.enabled === false) {
2804                                 error('charting library "' + this.library_name + '" is not available');
2805                                 if(this.debug === true)
2806                                         this.log('My chart library ' + this.library_name + ' is not available');
2807
2808                                 return false;
2809                         }
2810
2811                         if(this.isVisible() === false) {
2812                                 if(NETDATA.options.debug.visibility === true || this.debug === true)
2813                                         this.log('I am not visible');
2814
2815                                 return false;
2816                         }
2817
2818                         if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2819                                 if(this.debug === true)
2820                                         this.log('timed force update detected - allowing this update');
2821
2822                                 this.current.force_update_at = 0;
2823                                 return true;
2824                         }
2825
2826                         if(this.isAutoRefreshed() === true) {
2827                                 // allow the first update, even if the page is not visible
2828                                 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2829                                         if(NETDATA.options.debug.focus === true || this.debug === true)
2830                                                 this.log('canBeAutoRefreshed(): page does not have focus');
2831
2832                                         return false;
2833                                 }
2834
2835                                 if(this.needsRecreation() === true) {
2836                                         if(this.debug === true)
2837                                                 this.log('canBeAutoRefreshed(): needs re-creation.');
2838
2839                                         return true;
2840                                 }
2841
2842                                 // options valid only for autoRefresh()
2843                                 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
2844                                         if(NETDATA.globalPanAndZoom.isActive()) {
2845                                                 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
2846                                                         if(this.debug === true)
2847                                                                 this.log('canBeAutoRefreshed(): global panning: I need an update.');
2848
2849                                                         return true;
2850                                                 }
2851                                                 else {
2852                                                         if(this.debug === true)
2853                                                                 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
2854
2855                                                         return false;
2856                                                 }
2857                                         }
2858
2859                                         if(this.selected === true) {
2860                                                 if(this.debug === true)
2861                                                         this.log('canBeAutoRefreshed(): I have a selection in place.');
2862
2863                                                 return false;
2864                                         }
2865
2866                                         if(this.paused === true) {
2867                                                 if(this.debug === true)
2868                                                         this.log('canBeAutoRefreshed(): I am paused.');
2869
2870                                                 return false;
2871                                         }
2872
2873                                         if(now - this.tm.last_autorefreshed >= this.data_update_every) {
2874                                                 if(this.debug === true)
2875                                                         this.log('canBeAutoRefreshed(): It is time to update me.');
2876
2877                                                 return true;
2878                                         }
2879                                 }
2880                         }
2881
2882                         return false;
2883                 }
2884
2885                 this.autoRefresh = function(callback) {
2886                         if(this.canBeAutoRefreshed() === true) {
2887                                 this.updateChart(callback);
2888                         }
2889                         else {
2890                                 if(typeof callback !== 'undefined')
2891                                         callback();
2892                         }
2893                 }
2894
2895                 this._defaultsFromDownloadedChart = function(chart) {
2896                         this.chart = chart;
2897                         this.chart_url = chart.url;
2898                         this.data_update_every = chart.update_every * 1000;
2899                         this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2900                         this.tm.last_info_downloaded = new Date().getTime();
2901
2902                         if(this.title === null)
2903                                 this.title = chart.title;
2904
2905                         if(this.units === null)
2906                                 this.units = chart.units;
2907                 }
2908
2909                 // fetch the chart description from the netdata server
2910                 this.getChart = function(callback) {
2911                         this.chart = NETDATA.chartRegistry.get(this.host, this.id);
2912                         if(this.chart) {
2913                                 this._defaultsFromDownloadedChart(this.chart);
2914                                 if(typeof callback === 'function') callback();
2915                         }
2916                         else {
2917                                 this.chart_url = "/api/v1/chart?chart=" + this.id;
2918
2919                                 if(this.debug === true)
2920                                         this.log('downloading ' + this.chart_url);
2921
2922                                 $.ajax( {
2923                                         url:  this.host + this.chart_url,
2924                                         crossDomain: NETDATA.options.crossDomainAjax,
2925                                         cache: false,
2926                                         async: true
2927                                 })
2928                                 .done(function(chart) {
2929                                         chart.url = that.chart_url;
2930                                         that._defaultsFromDownloadedChart(chart);
2931                                         NETDATA.chartRegistry.add(that.host, that.id, chart);
2932                                 })
2933                                 .fail(function() {
2934                                         NETDATA.error(404, that.chart_url);
2935                                         error('chart not found on url "' + that.chart_url + '"');
2936                                 })
2937                                 .always(function() {
2938                                         if(typeof callback === 'function') callback();
2939                                 });
2940                         }
2941                 }
2942
2943                 // ============================================================================================================
2944                 // INITIALIZATION
2945
2946                 init();
2947         }
2948
2949         NETDATA.resetAllCharts = function(state) {
2950                 // first clear the global selection sync
2951                 // to make sure no chart is in selected state
2952                 state.globalSelectionSyncStop();
2953
2954                 // there are 2 possibilities here
2955                 // a. state is the global Pan and Zoom master
2956                 // b. state is not the global Pan and Zoom master
2957                 var master = true;
2958                 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
2959                         master = false;
2960
2961                 // clear the global Pan and Zoom
2962                 // this will also refresh the master
2963                 // and unblock any charts currently mirroring the master
2964                 NETDATA.globalPanAndZoom.clearMaster();
2965
2966                 // if we were not the master, reset our status too
2967                 // this is required because most probably the mouse
2968                 // is over this chart, blocking it from autorefreshing
2969                 if(master === false && (state.paused === true || state.selected === true))
2970                         state.resetChart();
2971         }
2972
2973         // get or create a chart state, given a DOM element
2974         NETDATA.chartState = function(element) {
2975                 var state = $(element).data('netdata-state-object') || null;
2976                 if(state === null) {
2977                         state = new chartState(element);
2978                         $(element).data('netdata-state-object', state);
2979                 }
2980                 return state;
2981         }
2982
2983         // ----------------------------------------------------------------------------------------------------------------
2984         // Library functions
2985
2986         // Load a script without jquery
2987         // This is used to load jquery - after it is loaded, we use jquery
2988         NETDATA._loadjQuery = function(callback) {
2989                 if(typeof jQuery === 'undefined') {
2990                         if(NETDATA.options.debug.main_loop === true)
2991                                 console.log('loading ' + NETDATA.jQuery);
2992
2993                         var script = document.createElement('script');
2994                         script.type = 'text/javascript';
2995                         script.async = true;
2996                         script.src = NETDATA.jQuery;
2997
2998                         // script.onabort = onError;
2999                         script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3000                         if(typeof callback === "function")
3001                                 script.onload = callback;
3002
3003                         var s = document.getElementsByTagName('script')[0];
3004                         s.parentNode.insertBefore(script, s);
3005                 }
3006                 else if(typeof callback === "function")
3007                         callback();
3008         }
3009
3010         NETDATA._loadCSS = function(filename) {
3011                 // don't use jQuery here
3012                 // styles are loaded before jQuery
3013                 // to eliminate showing an unstyled page to the user
3014
3015                 var fileref = document.createElement("link");
3016                 fileref.setAttribute("rel", "stylesheet");
3017                 fileref.setAttribute("type", "text/css");
3018                 fileref.setAttribute("href", filename);
3019
3020                 if (typeof fileref !== 'undefined')
3021                         document.getElementsByTagName("head")[0].appendChild(fileref);
3022         }
3023
3024         NETDATA.colorHex2Rgb = function(hex) {
3025                 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3026                 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3027                         hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3028                         return r + r + g + g + b + b;
3029                 });
3030
3031                 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3032                 return result ? {
3033                         r: parseInt(result[1], 16),
3034                         g: parseInt(result[2], 16),
3035                         b: parseInt(result[3], 16)
3036                 } : null;
3037         }
3038
3039         NETDATA.colorLuminance = function(hex, lum) {
3040                 // validate hex string
3041                 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3042                 if (hex.length < 6)
3043                         hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3044
3045                 lum = lum || 0;
3046
3047                 // convert to decimal and change luminosity
3048                 var rgb = "#", c, i;
3049                 for (i = 0; i < 3; i++) {
3050                         c = parseInt(hex.substr(i*2,2), 16);
3051                         c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3052                         rgb += ("00"+c).substr(c.length);
3053                 }
3054
3055                 return rgb;
3056         }
3057
3058         NETDATA.guid = function() {
3059                 function s4() {
3060                         return Math.floor((1 + Math.random()) * 0x10000)
3061                                         .toString(16)
3062                                         .substring(1);
3063                         }
3064
3065                         return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3066         }
3067
3068         NETDATA.zeropad = function(x) {
3069                 if(x > -10 && x < 10) return '0' + x.toString();
3070                 else return x.toString();
3071         }
3072
3073         // user function to signal us the DOM has been
3074         // updated.
3075         NETDATA.updatedDom = function() {
3076                 NETDATA.options.updated_dom = true;
3077         }
3078
3079         NETDATA.ready = function(callback) {
3080                 NETDATA.options.pauseCallback = callback;
3081         }
3082
3083         NETDATA.pause = function(callback) {
3084                 if(NETDATA.options.pause === true)
3085                         callback();
3086                 else
3087                         NETDATA.options.pauseCallback = callback;
3088         }
3089
3090         NETDATA.unpause = function() {
3091                 NETDATA.options.pauseCallback = null;
3092                 NETDATA.options.updated_dom = true;
3093                 NETDATA.options.pause = false;
3094         }
3095
3096         // ----------------------------------------------------------------------------------------------------------------
3097
3098         // this is purely sequencial charts refresher
3099         // it is meant to be autonomous
3100         NETDATA.chartRefresherNoParallel = function(index) {
3101                 if(NETDATA.options.debug.mail_loop === true)
3102                         console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3103
3104                 if(NETDATA.options.updated_dom === true) {
3105                         // the dom has been updated
3106                         // get the dom parts again
3107                         NETDATA.parseDom(NETDATA.chartRefresher);
3108                         return;
3109                 }
3110                 if(index >= NETDATA.options.targets.length) {
3111                         if(NETDATA.options.debug.main_loop === true)
3112                                 console.log('waiting to restart main loop...');
3113
3114                         NETDATA.options.auto_refresher_fast_weight = 0;
3115
3116                         setTimeout(function() {
3117                                 NETDATA.chartRefresher();
3118                         }, NETDATA.options.current.idle_between_loops);
3119                 }
3120                 else {
3121                         var state = NETDATA.options.targets[index];
3122
3123                         if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3124                                 if(NETDATA.options.debug.main_loop === true)
3125                                         console.log('fast rendering...');
3126
3127                                 state.autoRefresh(function() {
3128                                         NETDATA.chartRefresherNoParallel(++index);
3129                                 });
3130                         }
3131                         else {
3132                                 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3133                                 NETDATA.options.auto_refresher_fast_weight = 0;
3134
3135                                 setTimeout(function() {
3136                                         state.autoRefresh(function() {
3137                                                 NETDATA.chartRefresherNoParallel(++index);
3138                                         });
3139                                 }, NETDATA.options.current.idle_between_charts);
3140                         }
3141                 }
3142         }
3143
3144         // this is part of the parallel refresher
3145         // its cause is to refresh sequencially all the charts
3146         // that depend on chart library initialization
3147         // it will call the parallel refresher back
3148         // as soon as it sees a chart that its chart library
3149         // is initialized
3150         NETDATA.chartRefresher_unitialized = function() {
3151                 if(NETDATA.options.updated_dom === true) {
3152                         // the dom has been updated
3153                         // get the dom parts again
3154                         NETDATA.parseDom(NETDATA.chartRefresher);
3155                         return;
3156                 }
3157
3158                 if(NETDATA.options.sequencial.length === 0)
3159                         NETDATA.chartRefresher();
3160                 else {
3161                         var state = NETDATA.options.sequencial.pop();
3162                         if(state.library.initialized === true)
3163                                 NETDATA.chartRefresher();
3164                         else
3165                                 state.autoRefresh(NETDATA.chartRefresher_unitialized);
3166                 }
3167         }
3168
3169         NETDATA.chartRefresherWaitTime = function() {
3170                 return NETDATA.options.current.idle_parallel_loops;
3171         }
3172
3173         // the default refresher
3174         // it will create 2 sets of charts:
3175         // - the ones that can be refreshed in parallel
3176         // - the ones that depend on something else
3177         // the first set will be executed in parallel
3178         // the second will be given to NETDATA.chartRefresher_unitialized()
3179         NETDATA.chartRefresher = function() {
3180                 if(NETDATA.options.pause === true) {
3181                         // console.log('auto-refresher is paused');
3182                         setTimeout(NETDATA.chartRefresher,
3183                                 NETDATA.chartRefresherWaitTime());
3184                         return;
3185                 }
3186
3187                 if(typeof NETDATA.options.pauseCallback === 'function') {
3188                         // console.log('auto-refresher is calling pauseCallback');
3189                         NETDATA.options.pause = true;
3190                         NETDATA.options.pauseCallback();
3191                         NETDATA.chartRefresher();
3192                         return;
3193                 }
3194
3195                 if(NETDATA.options.current.parallel_refresher === false) {
3196                         NETDATA.chartRefresherNoParallel(0);
3197                         return;
3198                 }
3199
3200                 if(NETDATA.options.updated_dom === true) {
3201                         // the dom has been updated
3202                         // get the dom parts again
3203                         NETDATA.parseDom(NETDATA.chartRefresher);
3204                         return;
3205                 }
3206
3207                 var parallel = new Array();
3208                 var targets = NETDATA.options.targets;
3209                 var len = targets.length;
3210                 while(len--) {
3211                         if(targets[len].isVisible() === false)
3212                                 continue;
3213
3214                         var state = targets[len];
3215                         if(state.library.initialized === false) {
3216                                 if(state.library.enabled === true) {
3217                                         state.library.initialize(NETDATA.chartRefresher);
3218                                         return;
3219                                 }
3220                                 else {
3221                                         state.error('chart library "' + state.library_name + '" is not enabled.');
3222                                 }
3223                         }
3224
3225                         parallel.unshift(state);
3226                 }
3227
3228                 if(parallel.length > 0) {
3229                         var parallel_jobs = parallel.length;
3230
3231                         // this will execute the jobs in parallel
3232                         $(parallel).each(function() {
3233                                 this.autoRefresh(function() {
3234                                         parallel_jobs--;
3235
3236                                         if(parallel_jobs === 0) {
3237                                                 setTimeout(NETDATA.chartRefresher,
3238                                                         NETDATA.chartRefresherWaitTime());
3239                                         }
3240                                 });
3241                         })
3242                 }
3243                 else {
3244                         setTimeout(NETDATA.chartRefresher,
3245                                 NETDATA.chartRefresherWaitTime());
3246                 }
3247         }
3248
3249         NETDATA.parseDom = function(callback) {
3250                 NETDATA.options.last_page_scroll = new Date().getTime();
3251                 NETDATA.options.updated_dom = false;
3252
3253                 var targets = $('div[data-netdata]'); //.filter(':visible');
3254
3255                 if(NETDATA.options.debug.main_loop === true)
3256                         console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3257
3258                 NETDATA.options.targets = new Array();
3259                 var len = targets.length;
3260                 while(len--) {
3261                         // the initialization will take care of sizing
3262                         // and the "loading..." message
3263                         NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3264                 }
3265
3266                 if(typeof callback === 'function') callback();
3267         }
3268
3269         // this is the main function - where everything starts
3270         NETDATA.start = function() {
3271                 // this should be called only once
3272
3273                 NETDATA.options.page_is_visible = true;
3274
3275                 $(window).blur(function() {
3276                         if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3277                                 NETDATA.options.page_is_visible = false;
3278                                 if(NETDATA.options.debug.focus === true)
3279                                         console.log('Lost Focus!');
3280                         }
3281                 });
3282
3283                 $(window).focus(function() {
3284                         if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3285                                 NETDATA.options.page_is_visible = true;
3286                                 if(NETDATA.options.debug.focus === true)
3287                                         console.log('Focus restored!');
3288                         }
3289                 });
3290
3291                 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3292                         if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3293                                 NETDATA.options.page_is_visible = false;
3294                                 if(NETDATA.options.debug.focus === true)
3295                                         console.log('Document has no focus!');
3296                         }
3297                 }
3298
3299                 // bootstrap tab switching
3300                 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3301
3302                 // bootstrap modal switching
3303                 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3304                 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3305
3306                 NETDATA.parseDom(NETDATA.chartRefresher);
3307         }
3308
3309         // ----------------------------------------------------------------------------------------------------------------
3310         // peity
3311
3312         NETDATA.peityInitialize = function(callback) {
3313                 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3314                         $.ajax({
3315                                 url: NETDATA.peity_js,
3316                                 cache: true,
3317                                 dataType: "script"
3318                         })
3319                         .done(function() {
3320                                 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3321                         })
3322                         .fail(function() {
3323                                 NETDATA.chartLibraries.peity.enabled = false;
3324                                 NETDATA.error(100, NETDATA.peity_js);
3325                         })
3326                         .always(function() {
3327                                 if(typeof callback === "function")
3328                                         callback();
3329                         });
3330                 }
3331                 else {
3332                         NETDATA.chartLibraries.peity.enabled = false;
3333                         if(typeof callback === "function")
3334                                 callback();
3335                 }
3336         };
3337
3338         NETDATA.peityChartUpdate = function(state, data) {
3339                 state.peity_instance.innerHTML = data.result;
3340
3341                 if(state.peity_options.stroke !== state.chartColors()[0]) {
3342                         state.peity_options.stroke = state.chartColors()[0];
3343                         if(state.chart.chart_type === 'line')
3344                                 state.peity_options.fill = NETDATA.themes.current.background;
3345                         else
3346                                 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3347                 }
3348
3349                 $(state.peity_instance).peity('line', state.peity_options);
3350                 return true;
3351         }
3352
3353         NETDATA.peityChartCreate = function(state, data) {
3354                 state.peity_instance = document.createElement('div');
3355                 state.element_chart.appendChild(state.peity_instance);
3356
3357                 var self = $(state.element);
3358                 state.peity_options = {
3359                         stroke: NETDATA.themes.current.foreground,
3360                         strokeWidth: self.data('peity-strokewidth') || 1,
3361                         width: state.chartWidth(),
3362                         height: state.chartHeight(),
3363                         fill: NETDATA.themes.current.foreground
3364                 };
3365
3366                 NETDATA.peityChartUpdate(state, data);
3367                 return true;
3368         }
3369
3370         // ----------------------------------------------------------------------------------------------------------------
3371         // sparkline
3372
3373         NETDATA.sparklineInitialize = function(callback) {
3374                 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3375                         $.ajax({
3376                                 url: NETDATA.sparkline_js,
3377                                 cache: true,
3378                                 dataType: "script"
3379                         })
3380                         .done(function() {
3381                                 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3382                         })
3383                         .fail(function() {
3384                                 NETDATA.chartLibraries.sparkline.enabled = false;
3385                                 NETDATA.error(100, NETDATA.sparkline_js);
3386                         })
3387                         .always(function() {
3388                                 if(typeof callback === "function")
3389                                         callback();
3390                         });
3391                 }
3392                 else {
3393                         NETDATA.chartLibraries.sparkline.enabled = false;
3394                         if(typeof callback === "function")
3395                                 callback();
3396                 }
3397         };
3398
3399         NETDATA.sparklineChartUpdate = function(state, data) {
3400                 state.sparkline_options.width = state.chartWidth();
3401                 state.sparkline_options.height = state.chartHeight();
3402
3403                 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3404                 return true;
3405         }
3406
3407         NETDATA.sparklineChartCreate = function(state, data) {
3408                 var self = $(state.element);
3409                 var type = self.data('sparkline-type') || 'line';
3410                 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3411                 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3412                 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3413                 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3414                 var composite = self.data('sparkline-composite') || undefined;
3415                 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3416                 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3417                 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3418                 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3419                 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3420                 var spotColor = self.data('sparkline-spotcolor') || undefined;
3421                 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3422                 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3423                 var spotRadius = self.data('sparkline-spotradius') || undefined;
3424                 var valueSpots = self.data('sparkline-valuespots') || undefined;
3425                 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3426                 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3427                 var lineWidth = self.data('sparkline-linewidth') || undefined;
3428                 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3429                 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3430                 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3431                 var xvalues = self.data('sparkline-xvalues') || undefined;
3432                 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3433                 var xvalues = self.data('sparkline-xvalues') || undefined;
3434                 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3435                 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3436                 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3437                 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3438                 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3439                 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3440                 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3441                 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3442                 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3443                 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3444                 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3445                 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3446                 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3447                 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3448                 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3449                 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3450                 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3451                 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3452                 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3453                 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3454                 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3455
3456                 state.sparkline_options = {
3457                         type: type,
3458                         lineColor: lineColor,
3459                         fillColor: fillColor,
3460                         chartRangeMin: chartRangeMin,
3461                         chartRangeMax: chartRangeMax,
3462                         composite: composite,
3463                         enableTagOptions: enableTagOptions,
3464                         tagOptionPrefix: tagOptionPrefix,
3465                         tagValuesAttribute: tagValuesAttribute,
3466                         disableHiddenCheck: disableHiddenCheck,
3467                         defaultPixelsPerValue: defaultPixelsPerValue,
3468                         spotColor: spotColor,
3469                         minSpotColor: minSpotColor,
3470                         maxSpotColor: maxSpotColor,
3471                         spotRadius: spotRadius,
3472                         valueSpots: valueSpots,
3473                         highlightSpotColor: highlightSpotColor,
3474                         highlightLineColor: highlightLineColor,
3475                         lineWidth: lineWidth,
3476                         normalRangeMin: normalRangeMin,
3477                         normalRangeMax: normalRangeMax,
3478                         drawNormalOnTop: drawNormalOnTop,
3479                         xvalues: xvalues,
3480                         chartRangeClip: chartRangeClip,
3481                         chartRangeMinX: chartRangeMinX,
3482                         chartRangeMaxX: chartRangeMaxX,
3483                         disableInteraction: disableInteraction,
3484                         disableTooltips: disableTooltips,
3485                         disableHighlight: disableHighlight,
3486                         highlightLighten: highlightLighten,
3487                         highlightColor: highlightColor,
3488                         tooltipContainer: tooltipContainer,
3489                         tooltipClassname: tooltipClassname,
3490                         tooltipChartTitle: state.title,
3491                         tooltipFormat: tooltipFormat,
3492                         tooltipPrefix: tooltipPrefix,
3493                         tooltipSuffix: tooltipSuffix,
3494                         tooltipSkipNull: tooltipSkipNull,
3495                         tooltipValueLookups: tooltipValueLookups,
3496                         tooltipFormatFieldlist: tooltipFormatFieldlist,
3497                         tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3498                         numberFormatter: numberFormatter,
3499                         numberDigitGroupSep: numberDigitGroupSep,
3500                         numberDecimalMark: numberDecimalMark,
3501                         numberDigitGroupCount: numberDigitGroupCount,
3502                         animatedZooms: animatedZooms,
3503                         width: state.chartWidth(),
3504                         height: state.chartHeight()
3505                 };
3506
3507                 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3508                 return true;
3509         };
3510
3511         // ----------------------------------------------------------------------------------------------------------------
3512         // dygraph
3513
3514         NETDATA.dygraph = {
3515                 smooth: false
3516         };
3517
3518         NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3519                 if(after < state.netdata_first)
3520                         after = state.netdata_first;
3521
3522                 if(before > state.netdata_last)
3523                         before = state.netdata_last;
3524
3525                 state.setMode('zoom');
3526                 state.globalSelectionSyncStop();
3527                 state.globalSelectionSyncDelay();
3528                 state.dygraph_user_action = true;
3529                 state.dygraph_force_zoom = true;
3530                 state.updateChartPanOrZoom(after, before);
3531                 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3532         };
3533
3534         NETDATA.dygraphSetSelection = function(state, t) {
3535                 if(typeof state.dygraph_instance !== 'undefined') {
3536                         var r = state.calculateRowForTime(t);
3537                         if(r !== -1)
3538                                 state.dygraph_instance.setSelection(r);
3539                         else {
3540                                 state.dygraph_instance.clearSelection();
3541                                 state.legendShowUndefined();
3542                         }
3543                 }
3544
3545                 return true;
3546         };
3547
3548         NETDATA.dygraphClearSelection = function(state, t) {
3549                 if(typeof state.dygraph_instance !== 'undefined') {
3550                         state.dygraph_instance.clearSelection();
3551                 }
3552                 return true;
3553         };
3554
3555         NETDATA.dygraphSmoothInitialize = function(callback) {
3556                 $.ajax({
3557                         url: NETDATA.dygraph_smooth_js,
3558                         cache: true,
3559                         dataType: "script"
3560                 })
3561                 .done(function() {
3562                         NETDATA.dygraph.smooth = true;
3563                         smoothPlotter.smoothing = 0.3;
3564                 })
3565                 .fail(function() {
3566                         NETDATA.dygraph.smooth = false;
3567                 })
3568                 .always(function() {
3569                         if(typeof callback === "function")
3570                                 callback();
3571                 });
3572         }
3573
3574         NETDATA.dygraphInitialize = function(callback) {
3575                 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3576                         $.ajax({
3577                                 url: NETDATA.dygraph_js,
3578                                 cache: true,
3579                                 dataType: "script"
3580                         })
3581                         .done(function() {
3582                                 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3583                         })
3584                         .fail(function() {
3585                                 NETDATA.chartLibraries.dygraph.enabled = false;
3586                                 NETDATA.error(100, NETDATA.dygraph_js);
3587                         })
3588                         .always(function() {
3589                                 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3590                                         NETDATA.dygraphSmoothInitialize(callback);
3591                                 else if(typeof callback === "function")
3592                                         callback();
3593                         });
3594                 }
3595                 else {
3596                         NETDATA.chartLibraries.dygraph.enabled = false;
3597                         if(typeof callback === "function")
3598                                 callback();
3599                 }
3600         };
3601
3602         NETDATA.dygraphChartUpdate = function(state, data) {
3603                 var dygraph = state.dygraph_instance;
3604
3605                 if(typeof dygraph === 'undefined')
3606                         return NETDATA.dygraphChartCreate(state, data);
3607
3608                 // when the chart is not visible, and hidden
3609                 // if there is a window resize, dygraph detects
3610                 // its element size as 0x0.
3611                 // this will make it re-appear properly
3612
3613                 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3614                         dygraph.resize();
3615
3616                 var options = {
3617                                 file: data.result.data,
3618                                 colors: state.chartColors(),
3619                                 labels: data.result.labels,
3620                                 labelsDivWidth: state.chartWidth() - 70,
3621                                 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3622                 };
3623
3624                 if(state.dygraph_force_zoom === true) {
3625                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
3626                                 state.log('dygraphChartUpdate() forced zoom update');
3627
3628                         options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3629                         options.valueRange = null;
3630                         options.isZoomedIgnoreProgrammaticZoom = true;
3631                         state.dygraph_force_zoom = false;
3632                 }
3633                 else if(state.current.name !== 'auto') {
3634                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
3635                                 state.log('dygraphChartUpdate() loose update');
3636                 }
3637                 else {
3638                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
3639                                 state.log('dygraphChartUpdate() strict update');
3640
3641                         options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3642                         options.valueRange = null;
3643                         options.isZoomedIgnoreProgrammaticZoom = true;
3644                 }
3645
3646                 if(state.dygraph_smooth_eligible === true) {
3647                         if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3648                                 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3649                                 NETDATA.dygraphChartCreate(state, data);
3650                                 return;
3651                         }
3652                 }
3653
3654                 dygraph.updateOptions(options);
3655
3656                 state.dygraph_last_rendered = new Date().getTime();
3657                 return true;
3658         };
3659
3660         NETDATA.dygraphChartCreate = function(state, data) {
3661                 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3662                         state.log('dygraphChartCreate()');
3663
3664                 var self = $(state.element);
3665
3666                 var chart_type = state.chart.chart_type;
3667                 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3668                 chart_type = self.data('dygraph-type') || chart_type;
3669
3670                 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3671                 smooth = self.data('dygraph-smooth') || smooth;
3672
3673                 if(NETDATA.dygraph.smooth === false)
3674                         smooth = false;
3675
3676                 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3677                 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3678
3679                 state.dygraph_options = {
3680                         colors: self.data('dygraph-colors') || state.chartColors(),
3681
3682                         // leave a few pixels empty on the right of the chart
3683                         rightGap: self.data('dygraph-rightgap') || 5,
3684                         showRangeSelector: self.data('dygraph-showrangeselector') || false,
3685                         showRoller: self.data('dygraph-showroller') || false,
3686
3687                         title: self.data('dygraph-title') || state.title,
3688                         titleHeight: self.data('dygraph-titleheight') || 19,
3689
3690                         legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3691                         labels: data.result.labels,
3692                         labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3693                         labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3694                         labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3695                         labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3696                         labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3697                         labelsKMB: false,
3698                         labelsKMG2: false,
3699                         showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3700                         hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3701
3702                         ylabel: state.units,
3703                         yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3704
3705                         // the function to plot the chart
3706                         plotter: null,
3707
3708                         // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3709                         strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3710                         strokePattern: self.data('dygraph-strokepattern') || undefined,
3711
3712                         // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3713                         // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3714                         drawPoints: self.data('dygraph-drawpoints') || false,
3715
3716                         // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3717                         drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3718
3719                         connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3720                         pointSize: self.data('dygraph-pointsize') || 1,
3721
3722                         // enabling this makes the chart with little square lines
3723                         stepPlot: self.data('dygraph-stepplot') || false,
3724
3725                         // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3726                         strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3727                         strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3728
3729                         fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3730                         fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3731                         stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3732                         stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3733
3734                         drawAxis: self.data('dygraph-drawaxis') || true,
3735                         axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3736                         axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3737                         axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3738
3739                         drawGrid: self.data('dygraph-drawgrid') || true,
3740                         drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3741                         drawYGrid: self.data('dygraph-drawygrid') || undefined,
3742                         gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3743                         gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3744                         gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3745
3746                         maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3747                         sigFigs: self.data('dygraph-sigfigs') || null,
3748                         digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3749                         valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3750
3751                         highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3752                         highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3753                         highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3754
3755                         pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3756                         visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3757                         axes: {
3758                                 x: {
3759                                         pixelsPerLabel: 50,
3760                                         ticker: Dygraph.dateTicker,
3761                                         axisLabelFormatter: function (d, gran) {
3762                                                 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3763                                         },
3764                                         valueFormatter: function (ms) {
3765                                                 var d = new Date(ms);
3766                                                 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3767                                                 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3768                                         }
3769                                 },
3770                                 y: {
3771                                         pixelsPerLabel: 15,
3772                                         valueFormatter: function (x) {
3773                                                 // we format legends with the state object
3774                                                 // no need to do anything here
3775                                                 // return (Math.round(x*100) / 100).toLocaleString();
3776                                                 // return state.legendFormatValue(x);
3777                                                 return x;
3778                                         }
3779                                 }
3780                         },
3781                         legendFormatter: function(data) {
3782                                 var elements = state.element_legend_childs;
3783
3784                                 // if the hidden div is not there
3785                                 // we are not managing the legend
3786                                 if(elements.hidden === null) return;
3787
3788                                 if (typeof data.x !== 'undefined') {
3789                                         state.legendSetDate(data.x);
3790                                         var i = data.series.length;
3791                                         while(i--) {
3792                                                 var series = data.series[i];
3793                                                 if(!series.isVisible) continue;
3794                                                 state.legendSetLabelValue(series.label, series.y);
3795                                         }
3796                                 }
3797
3798                                 return '';
3799                         },
3800                         drawCallback: function(dygraph, is_initial) {
3801                                 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3802                                         state.dygraph_user_action = false;
3803
3804                                         var x_range = dygraph.xAxisRange();
3805                                         var after = Math.round(x_range[0]);
3806                                         var before = Math.round(x_range[1]);
3807
3808                                         if(NETDATA.options.debug.dygraph === true)
3809                                                 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3810
3811                                         if(before <= state.netdata_last && after >= state.netdata_first)
3812                                                 state.updateChartPanOrZoom(after, before);
3813                                 }
3814                         },
3815                         zoomCallback: function(minDate, maxDate, yRanges) {
3816                                 if(NETDATA.options.debug.dygraph === true)
3817                                         state.log('dygraphZoomCallback()');
3818
3819                                 state.globalSelectionSyncStop();
3820                                 state.globalSelectionSyncDelay();
3821                                 state.setMode('zoom');
3822
3823                                 // refresh it to the greatest possible zoom level
3824                                 state.dygraph_user_action = true;
3825                                 state.dygraph_force_zoom = true;
3826                                 state.updateChartPanOrZoom(minDate, maxDate);
3827                         },
3828                         highlightCallback: function(event, x, points, row, seriesName) {
3829                                 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3830                                         state.log('dygraphHighlightCallback()');
3831
3832                                 state.pauseChart();
3833
3834                                 // there is a bug in dygraph when the chart is zoomed enough
3835                                 // the time it thinks is selected is wrong
3836                                 // here we calculate the time t based on the row number selected
3837                                 // which is ok
3838                                 var t = state.data_after + row * state.data_update_every;
3839                                 // 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);
3840
3841                                 state.globalSelectionSync(x);
3842
3843                                 // fix legend zIndex using the internal structures of dygraph legend module
3844                                 // this works, but it is a hack!
3845                                 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
3846                         },
3847                         unhighlightCallback: function(event) {
3848                                 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3849                                         state.log('dygraphUnhighlightCallback()');
3850
3851                                 state.unpauseChart();
3852                                 state.globalSelectionSyncStop();
3853                         },
3854                         interactionModel : {
3855                                 mousedown: function(event, dygraph, context) {
3856                                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
3857                                                 state.log('interactionModel.mousedown()');
3858
3859                                         state.dygraph_user_action = true;
3860                                         state.globalSelectionSyncStop();
3861
3862                                         if(NETDATA.options.debug.dygraph === true)
3863                                                 state.log('dygraphMouseDown()');
3864
3865                                         // Right-click should not initiate a zoom.
3866                                         if(event.button && event.button === 2) return;
3867
3868                                         context.initializeMouseDown(event, dygraph, context);
3869
3870                                         if(event.button && event.button === 1) {
3871                                                 if (event.altKey || event.shiftKey) {
3872                                                         state.setMode('pan');
3873                                                         state.globalSelectionSyncDelay();
3874                                                         Dygraph.startPan(event, dygraph, context);
3875                                                 }
3876                                                 else {
3877                                                         state.setMode('zoom');
3878                                                         state.globalSelectionSyncDelay();
3879                                                         Dygraph.startZoom(event, dygraph, context);
3880                                                 }
3881                                         }
3882                                         else {
3883                                                 if (event.altKey || event.shiftKey) {
3884                                                         state.setMode('zoom');
3885                                                         state.globalSelectionSyncDelay();
3886                                                         Dygraph.startZoom(event, dygraph, context);
3887                                                 }
3888                                                 else {
3889                                                         state.setMode('pan');
3890                                                         state.globalSelectionSyncDelay();
3891                                                         Dygraph.startPan(event, dygraph, context);
3892                                                 }
3893                                         }
3894                                 },
3895                                 mousemove: function(event, dygraph, context) {
3896                                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
3897                                                 state.log('interactionModel.mousemove()');
3898
3899                                         if(context.isPanning) {
3900                                                 state.dygraph_user_action = true;
3901                                                 state.globalSelectionSyncStop();
3902                                                 state.globalSelectionSyncDelay();
3903                                                 state.setMode('pan');
3904                                                 Dygraph.movePan(event, dygraph, context);
3905                                         }
3906                                         else if(context.isZooming) {
3907                                                 state.dygraph_user_action = true;
3908                                                 state.globalSelectionSyncStop();
3909                                                 state.globalSelectionSyncDelay();
3910                                                 state.setMode('zoom');
3911                                                 Dygraph.moveZoom(event, dygraph, context);
3912                                         }
3913                                 },
3914                                 mouseup: function(event, dygraph, context) {
3915                                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
3916                                                 state.log('interactionModel.mouseup()');
3917
3918                                         if (context.isPanning) {
3919                                                 state.dygraph_user_action = true;
3920                                                 state.globalSelectionSyncDelay();
3921                                                 Dygraph.endPan(event, dygraph, context);
3922                                         }
3923                                         else if (context.isZooming) {
3924                                                 state.dygraph_user_action = true;
3925                                                 state.globalSelectionSyncDelay();
3926                                                 Dygraph.endZoom(event, dygraph, context);
3927                                         }
3928                                 },
3929                                 click: function(event, dygraph, context) {
3930                                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
3931                                                 state.log('interactionModel.click()');
3932
3933                                         event.preventDefault();
3934                                 },
3935                                 dblclick: function(event, dygraph, context) {
3936                                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
3937                                                 state.log('interactionModel.dblclick()');
3938                                         NETDATA.resetAllCharts(state);
3939                                 },
3940                                 mousewheel: function(event, dygraph, context) {
3941                                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
3942                                                 state.log('interactionModel.mousewheel()');
3943
3944                                         // Take the offset of a mouse event on the dygraph canvas and
3945                                         // convert it to a pair of percentages from the bottom left.
3946                                         // (Not top left, bottom is where the lower value is.)
3947                                         function offsetToPercentage(g, offsetX, offsetY) {
3948                                                 // This is calculating the pixel offset of the leftmost date.
3949                                                 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
3950                                                 var yar0 = g.yAxisRange(0);
3951
3952                                                 // This is calculating the pixel of the higest value. (Top pixel)
3953                                                 var yOffset = g.toDomCoords(null, yar0[1])[1];
3954
3955                                                 // x y w and h are relative to the corner of the drawing area,
3956                                                 // so that the upper corner of the drawing area is (0, 0).
3957                                                 var x = offsetX - xOffset;
3958                                                 var y = offsetY - yOffset;
3959
3960                                                 // This is computing the rightmost pixel, effectively defining the
3961                                                 // width.
3962                                                 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
3963
3964                                                 // This is computing the lowest pixel, effectively defining the height.
3965                                                 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
3966
3967                                                 // Percentage from the left.
3968                                                 var xPct = w === 0 ? 0 : (x / w);
3969                                                 // Percentage from the top.
3970                                                 var yPct = h === 0 ? 0 : (y / h);
3971
3972                                                 // The (1-) part below changes it from "% distance down from the top"
3973                                                 // to "% distance up from the bottom".
3974                                                 return [xPct, (1-yPct)];
3975                                         }
3976
3977                                         // Adjusts [x, y] toward each other by zoomInPercentage%
3978                                         // Split it so the left/bottom axis gets xBias/yBias of that change and
3979                                         // tight/top gets (1-xBias)/(1-yBias) of that change.
3980                                         //
3981                                         // If a bias is missing it splits it down the middle.
3982                                         function zoomRange(g, zoomInPercentage, xBias, yBias) {
3983                                                 xBias = xBias || 0.5;
3984                                                 yBias = yBias || 0.5;
3985
3986                                                 function adjustAxis(axis, zoomInPercentage, bias) {
3987                                                         var delta = axis[1] - axis[0];
3988                                                         var increment = delta * zoomInPercentage;
3989                                                         var foo = [increment * bias, increment * (1-bias)];
3990
3991                                                         return [ axis[0] + foo[0], axis[1] - foo[1] ];
3992                                                 }
3993
3994                                                 var yAxes = g.yAxisRanges();
3995                                                 var newYAxes = [];
3996                                                 for (var i = 0; i < yAxes.length; i++) {
3997                                                         newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
3998                                                 }
3999
4000                                                 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4001                                         }
4002
4003                                         if(event.altKey || event.shiftKey) {
4004                                                 state.dygraph_user_action = true;
4005
4006                                                 state.globalSelectionSyncStop();
4007                                                 state.globalSelectionSyncDelay();
4008
4009                                                 // http://dygraphs.com/gallery/interaction-api.js
4010                                                 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4011                                                 var percentage = normal / 50;
4012
4013                                                 if (!(event.offsetX && event.offsetY)){
4014                                                         event.offsetX = event.layerX - event.target.offsetLeft;
4015                                                         event.offsetY = event.layerY - event.target.offsetTop;
4016                                                 }
4017
4018                                                 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4019                                                 var xPct = percentages[0];
4020                                                 var yPct = percentages[1];
4021
4022                                                 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4023
4024                                                 var after = new_x_range[0];
4025                                                 var before = new_x_range[1];
4026
4027                                                 var first = state.netdata_first + state.data_update_every;
4028                                                 var last = state.netdata_last + state.data_update_every;
4029
4030                                                 if(before > last) {
4031                                                         after -= (before - last);
4032                                                         before = last;
4033                                                 }
4034                                                 if(after < first) {
4035                                                         after = first;
4036                                                 }
4037
4038                                                 state.setMode('zoom');
4039                                                 if(state.updateChartPanOrZoom(after, before) === true)
4040                                                         dygraph.updateOptions({ dateWindow: [ after, before ] });
4041
4042                                                 event.preventDefault();
4043                                         }
4044                                 },
4045                                 touchstart: function(event, dygraph, context) {
4046                                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
4047                                                 state.log('interactionModel.touchstart()');
4048
4049                                         state.dygraph_user_action = true;
4050                                         state.setMode('zoom');
4051                                         state.pauseChart();
4052
4053                                         Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4054
4055                                         // we overwrite the touch directions at the end, to overwrite
4056                                         // the internal default of dygraphs
4057                                         context.touchDirections = { x: true, y: false };
4058
4059                                         state.dygraph_last_touch_start = new Date().getTime();
4060                                         state.dygraph_last_touch_move = 0;
4061
4062                                         if(typeof event.touches[0].pageX === 'number')
4063                                                 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4064                                         else
4065                                                 state.dygraph_last_touch_page_x = 0;
4066                                 },
4067                                 touchmove: function(event, dygraph, context) {
4068                                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
4069                                                 state.log('interactionModel.touchmove()');
4070
4071                                         state.dygraph_user_action = true;
4072                                         Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4073
4074                                         state.dygraph_last_touch_move = new Date().getTime();
4075                                 },
4076                                 touchend: function(event, dygraph, context) {
4077                                         if(NETDATA.options.debug.dygraph === true || state.debug === true)
4078                                                 state.log('interactionModel.touchend()');
4079
4080                                         state.dygraph_user_action = true;
4081                                         Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4082
4083                                         // if it didn't move, it is a selection
4084                                         if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4085                                                 // internal api of dygraphs
4086                                                 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4087                                                 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4088                                                 if(NETDATA.dygraphSetSelection(state, t) === true)
4089                                                         state.globalSelectionSync(t);
4090                                         }
4091
4092                                         // if it was double tap within double click time, reset the charts
4093                                         var now = new Date().getTime();
4094                                         if(typeof state.dygraph_last_touch_end !== 'undefined') {
4095                                                 if(state.dygraph_last_touch_move === 0) {
4096                                                         var dt = now - state.dygraph_last_touch_end;
4097                                                         if(dt <= NETDATA.options.current.double_click_speed)
4098                                                                 NETDATA.resetAllCharts(state);
4099                                                 }
4100                                         }
4101
4102                                         // remember the timestamp of the last touch end
4103                                         state.dygraph_last_touch_end = now;
4104                                 }
4105                         }
4106                 };
4107
4108                 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4109                         state.dygraph_options.drawGrid = false;
4110                         state.dygraph_options.drawAxis = false;
4111                         state.dygraph_options.title = undefined;
4112                         state.dygraph_options.units = undefined;
4113                         state.dygraph_options.ylabel = undefined;
4114                         state.dygraph_options.yLabelWidth = 0;
4115                         state.dygraph_options.labelsDivWidth = 120;
4116                         state.dygraph_options.labelsDivStyles.width = '120px';
4117                         state.dygraph_options.labelsSeparateLines = true;
4118                         state.dygraph_options.rightGap = 0;
4119                 }
4120
4121                 if(smooth === true) {
4122                         state.dygraph_smooth_eligible = true;
4123
4124                         if(NETDATA.options.current.smooth_plot === true)
4125                                 state.dygraph_options.plotter = smoothPlotter;
4126                 }
4127                 else state.dygraph_smooth_eligible = false;
4128
4129                 state.dygraph_instance = new Dygraph(state.element_chart,
4130                         data.result.data, state.dygraph_options);
4131
4132                 state.dygraph_force_zoom = false;
4133                 state.dygraph_user_action = false;
4134                 state.dygraph_last_rendered = new Date().getTime();
4135                 return true;
4136         };
4137
4138         // ----------------------------------------------------------------------------------------------------------------
4139         // morris
4140
4141         NETDATA.morrisInitialize = function(callback) {
4142                 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4143
4144                         // morris requires raphael
4145                         if(!NETDATA.chartLibraries.raphael.initialized) {
4146                                 if(NETDATA.chartLibraries.raphael.enabled) {
4147                                         NETDATA.raphaelInitialize(function() {
4148                                                 NETDATA.morrisInitialize(callback);
4149                                         });
4150                                 }
4151                                 else {
4152                                         NETDATA.chartLibraries.morris.enabled = false;
4153                                         if(typeof callback === "function")
4154                                                 callback();
4155                                 }
4156                         }
4157                         else {
4158                                 NETDATA._loadCSS(NETDATA.morris_css);
4159
4160                                 $.ajax({
4161                                         url: NETDATA.morris_js,
4162                                         cache: true,
4163                                         dataType: "script"
4164                                 })
4165                                 .done(function() {
4166                                         NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4167                                 })
4168                                 .fail(function() {
4169                                         NETDATA.chartLibraries.morris.enabled = false;
4170                                         NETDATA.error(100, NETDATA.morris_js);
4171                                 })
4172                                 .always(function() {
4173                                         if(typeof callback === "function")
4174                                                 callback();
4175                                 });
4176                         }
4177                 }
4178                 else {
4179                         NETDATA.chartLibraries.morris.enabled = false;
4180                         if(typeof callback === "function")
4181                                 callback();
4182                 }
4183         };
4184
4185         NETDATA.morrisChartUpdate = function(state, data) {
4186                 state.morris_instance.setData(data.result.data);
4187                 return true;
4188         };
4189
4190         NETDATA.morrisChartCreate = function(state, data) {
4191
4192                 state.morris_options = {
4193                                 element: state.element_chart.id,
4194                                 data: data.result.data,
4195                                 xkey: 'time',
4196                                 ykeys: data.dimension_names,
4197                                 labels: data.dimension_names,
4198                                 lineWidth: 2,
4199                                 pointSize: 3,
4200                                 smooth: true,
4201                                 hideHover: 'auto',
4202                                 parseTime: true,
4203                                 continuousLine: false,
4204                                 behaveLikeLine: false
4205                 };
4206
4207                 if(state.chart.chart_type === 'line')
4208                         state.morris_instance = new Morris.Line(state.morris_options);
4209
4210                 else if(state.chart.chart_type === 'area') {
4211                         state.morris_options.behaveLikeLine = true;
4212                         state.morris_instance = new Morris.Area(state.morris_options);
4213                 }
4214                 else // stacked
4215                         state.morris_instance = new Morris.Area(state.morris_options);
4216
4217                 return true;
4218         };
4219
4220         // ----------------------------------------------------------------------------------------------------------------
4221         // raphael
4222
4223         NETDATA.raphaelInitialize = function(callback) {
4224                 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4225                         $.ajax({
4226                                 url: NETDATA.raphael_js,
4227                                 cache: true,
4228                                 dataType: "script"
4229                         })
4230                         .done(function() {
4231                                 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4232                         })
4233                         .fail(function() {
4234                                 NETDATA.chartLibraries.raphael.enabled = false;
4235                                 NETDATA.error(100, NETDATA.raphael_js);
4236                         })
4237                         .always(function() {
4238                                 if(typeof callback === "function")
4239                                         callback();
4240                         });
4241                 }
4242                 else {
4243                         NETDATA.chartLibraries.raphael.enabled = false;
4244                         if(typeof callback === "function")
4245                                 callback();
4246                 }
4247         };
4248
4249         NETDATA.raphaelChartUpdate = function(state, data) {
4250                 $(state.element_chart).raphael(data.result, {
4251                         width: state.chartWidth(),
4252                         height: state.chartHeight()
4253                 });
4254
4255                 return false;
4256         };
4257
4258         NETDATA.raphaelChartCreate = function(state, data) {
4259                 $(state.element_chart).raphael(data.result, {
4260                         width: state.chartWidth(),
4261                         height: state.chartHeight()
4262                 });
4263
4264                 return false;
4265         };
4266
4267         // ----------------------------------------------------------------------------------------------------------------
4268         // C3
4269
4270         NETDATA.c3Initialize = function(callback) {
4271                 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4272
4273                         // C3 requires D3
4274                         if(!NETDATA.chartLibraries.d3.initialized) {
4275                                 if(NETDATA.chartLibraries.d3.enabled) {
4276                                         NETDATA.d3Initialize(function() {
4277                                                 NETDATA.c3Initialize(callback);
4278                                         });
4279                                 }
4280                                 else {
4281                                         NETDATA.chartLibraries.c3.enabled = false;
4282                                         if(typeof callback === "function")
4283                                                 callback();
4284                                 }
4285                         }
4286                         else {
4287                                 NETDATA._loadCSS(NETDATA.c3_css);
4288
4289                                 $.ajax({
4290                                         url: NETDATA.c3_js,
4291                                         cache: true,
4292                                         dataType: "script"
4293                                 })
4294                                 .done(function() {
4295                                         NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4296                                 })
4297                                 .fail(function() {
4298                                         NETDATA.chartLibraries.c3.enabled = false;
4299                                         NETDATA.error(100, NETDATA.c3_js);
4300                                 })
4301                                 .always(function() {
4302                                         if(typeof callback === "function")
4303                                                 callback();
4304                                 });
4305                         }
4306                 }
4307                 else {
4308                         NETDATA.chartLibraries.c3.enabled = false;
4309                         if(typeof callback === "function")
4310                                 callback();
4311                 }
4312         };
4313
4314         NETDATA.c3ChartUpdate = function(state, data) {
4315                 state.c3_instance.destroy();
4316                 return NETDATA.c3ChartCreate(state, data);
4317
4318                 //state.c3_instance.load({
4319                 //      rows: data.result,
4320                 //      unload: true
4321                 //});
4322
4323                 //return true;
4324         };
4325
4326         NETDATA.c3ChartCreate = function(state, data) {
4327
4328                 state.element_chart.id = 'c3-' + state.uuid;
4329                 // console.log('id = ' + state.element_chart.id);
4330
4331                 state.c3_instance = c3.generate({
4332                         bindto: '#' + state.element_chart.id,
4333                         size: {
4334                                 width: state.chartWidth(),
4335                                 height: state.chartHeight()
4336                         },
4337                         color: {
4338                                 pattern: state.chartColors()
4339                         },
4340                         data: {
4341                                 x: 'time',
4342                                 rows: data.result,
4343                                 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4344                         },
4345                         axis: {
4346                                 x: {
4347                                         type: 'timeseries',
4348                                         tick: {
4349                                                 format: function(x) {
4350                                                         return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4351                                                 }
4352                                         }
4353                                 }
4354                         },
4355                         grid: {
4356                                 x: {
4357                                         show: true
4358                                 },
4359                                 y: {
4360                                         show: true
4361                                 }
4362                         },
4363                         point: {
4364                                 show: false
4365                         },
4366                         line: {
4367                                 connectNull: false
4368                         },
4369                         transition: {
4370                                 duration: 0
4371                         },
4372                         interaction: {
4373                                 enabled: true
4374                         }
4375                 });
4376
4377                 // console.log(state.c3_instance);
4378
4379                 return true;
4380         };
4381
4382         // ----------------------------------------------------------------------------------------------------------------
4383         // D3
4384
4385         NETDATA.d3Initialize = function(callback) {
4386                 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4387                         $.ajax({
4388                                 url: NETDATA.d3_js,
4389                                 cache: true,
4390                                 dataType: "script"
4391                         })
4392                         .done(function() {
4393                                 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4394                         })
4395                         .fail(function() {
4396                                 NETDATA.chartLibraries.d3.enabled = false;
4397                                 NETDATA.error(100, NETDATA.d3_js);
4398                         })
4399                         .always(function() {
4400                                 if(typeof callback === "function")
4401                                         callback();
4402                         });
4403                 }
4404                 else {
4405                         NETDATA.chartLibraries.d3.enabled = false;
4406                         if(typeof callback === "function")
4407                                 callback();
4408                 }
4409         };
4410
4411         NETDATA.d3ChartUpdate = function(state, data) {
4412                 return false;
4413         };
4414
4415         NETDATA.d3ChartCreate = function(state, data) {
4416                 return false;
4417         };
4418
4419         // ----------------------------------------------------------------------------------------------------------------
4420         // google charts
4421
4422         NETDATA.googleInitialize = function(callback) {
4423                 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4424                         $.ajax({
4425                                 url: NETDATA.google_js,
4426                                 cache: true,
4427                                 dataType: "script"
4428                         })
4429                         .done(function() {
4430                                 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4431                                 google.load('visualization', '1.1', {
4432                                         'packages': ['corechart', 'controls'],
4433                                         'callback': callback
4434                                 });
4435                         })
4436                         .fail(function() {
4437                                 NETDATA.chartLibraries.google.enabled = false;
4438                                 NETDATA.error(100, NETDATA.google_js);
4439                                 if(typeof callback === "function")
4440                                         callback();
4441                         });
4442                 }
4443                 else {
4444                         NETDATA.chartLibraries.google.enabled = false;
4445                         if(typeof callback === "function")
4446                                 callback();
4447                 }
4448         };
4449
4450         NETDATA.googleChartUpdate = function(state, data) {
4451                 var datatable = new google.visualization.DataTable(data.result);
4452                 state.google_instance.draw(datatable, state.google_options);
4453                 return true;
4454         };
4455
4456         NETDATA.googleChartCreate = function(state, data) {
4457                 var datatable = new google.visualization.DataTable(data.result);
4458
4459                 state.google_options = {
4460                         colors: state.chartColors(),
4461
4462                         // do not set width, height - the chart resizes itself
4463                         //width: state.chartWidth(),
4464                         //height: state.chartHeight(),
4465                         lineWidth: 1,
4466                         title: state.title,
4467                         fontSize: 11,
4468                         hAxis: {
4469                         //      title: "Time of Day",
4470                         //      format:'HH:mm:ss',
4471                                 viewWindowMode: 'maximized',
4472                                 slantedText: false,
4473                                 format:'HH:mm:ss',
4474                                 textStyle: {
4475                                         fontSize: 9
4476                                 },
4477                                 gridlines: {
4478                                         color: '#EEE'
4479                                 }
4480                         },
4481                         vAxis: {
4482                                 title: state.units,
4483                                 viewWindowMode: 'pretty',
4484                                 minValue: -0.1,
4485                                 maxValue: 0.1,
4486                                 direction: 1,
4487                                 textStyle: {
4488                                         fontSize: 9
4489                                 },
4490                                 gridlines: {
4491                                         color: '#EEE'
4492                                 }
4493                         },
4494                         chartArea: {
4495                                 width: '65%',
4496                                 height: '80%'
4497                         },
4498                         focusTarget: 'category',
4499                         annotation: {
4500                                 '1': {
4501                                         style: 'line'
4502                                 }
4503                         },
4504                         pointsVisible: 0,
4505                         titlePosition: 'out',
4506                         titleTextStyle: {
4507                                 fontSize: 11
4508                         },
4509                         tooltip: {
4510                                 isHtml: false,
4511                                 ignoreBounds: true,
4512                                 textStyle: {
4513                                         fontSize: 9
4514                                 }
4515                         },
4516                         curveType: 'function',
4517                         areaOpacity: 0.3,
4518                         isStacked: false
4519                 };
4520
4521                 switch(state.chart.chart_type) {
4522                         case "area":
4523                                 state.google_options.vAxis.viewWindowMode = 'maximized';
4524                                 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4525                                 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4526                                 break;
4527
4528                         case "stacked":
4529                                 state.google_options.isStacked = true;
4530                                 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4531                                 state.google_options.vAxis.viewWindowMode = 'maximized';
4532                                 state.google_options.vAxis.minValue = null;
4533                                 state.google_options.vAxis.maxValue = null;
4534                                 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4535                                 break;
4536
4537                         default:
4538                         case "line":
4539                                 state.google_options.lineWidth = 2;
4540                                 state.google_instance = new google.visualization.LineChart(state.element_chart);
4541                                 break;
4542                 }
4543
4544                 state.google_instance.draw(datatable, state.google_options);
4545                 return true;
4546         };
4547
4548         // ----------------------------------------------------------------------------------------------------------------
4549
4550         NETDATA.percentFromValueMax = function(value, max) {
4551                 if(value === null) value = 0;
4552                 if(max < value) max = value;
4553
4554                 var pcent = 0;
4555                 if(max !== 0) {
4556                         pcent = Math.round(value * 100 / max);
4557                         if(pcent === 0 && value > 0) pcent = 1;
4558                 }
4559
4560                 return pcent;
4561         }
4562
4563         // ----------------------------------------------------------------------------------------------------------------
4564         // easy-pie-chart
4565
4566         NETDATA.easypiechartInitialize = function(callback) {
4567                 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4568                         $.ajax({
4569                                 url: NETDATA.easypiechart_js,
4570                                 cache: true,
4571                                 dataType: "script"
4572                         })
4573                                 .done(function() {
4574                                         NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4575                                 })
4576                                 .fail(function() {
4577                                         NETDATA.chartLibraries.easypiechart.enabled = false;
4578                                         NETDATA.error(100, NETDATA.easypiechart_js);
4579                                 })
4580                                 .always(function() {
4581                                         if(typeof callback === "function")
4582                                                 callback();
4583                                 })
4584                 }
4585                 else {
4586                         NETDATA.chartLibraries.easypiechart.enabled = false;
4587                         if(typeof callback === "function")
4588                                 callback();
4589                 }
4590         };
4591
4592         NETDATA.easypiechartClearSelection = function(state) {
4593                 if(typeof state.easyPieChartEvent !== 'undefined') {
4594                         if(state.easyPieChartEvent.timer !== null)
4595                                 clearTimeout(state.easyPieChartEvent.timer);
4596
4597                         state.easyPieChartEvent.timer = null;
4598                 }
4599
4600                 if(state.isAutoRefreshed() === true && state.data !== null) {
4601                         NETDATA.easypiechartChartUpdate(state, state.data);
4602                 }
4603                 else {
4604                         state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4605                         state.easyPieChart_instance.update(0);
4606                 }
4607                 state.easyPieChart_instance.enableAnimation();
4608
4609                 return true;
4610         };
4611
4612         NETDATA.easypiechartSetSelection = function(state, t) {
4613                 if(state.timeIsVisible(t) !== true)
4614                         return NETDATA.easypiechartClearSelection(state);
4615
4616                 var slot = state.calculateRowForTime(t);
4617                 if(slot < 0 || slot >= state.data.result.length)
4618                         return NETDATA.easypiechartClearSelection(state);
4619
4620                 if(typeof state.easyPieChartEvent === 'undefined') {
4621                         state.easyPieChartEvent = {
4622                                 timer: null,
4623                                 value: 0,
4624                                 pcent: 0,
4625                         };
4626                 }
4627
4628                 var value = state.data.result[state.data.result.length - 1 - slot];
4629                 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4630                 var pcent = NETDATA.percentFromValueMax(value, max);
4631
4632                 state.easyPieChartEvent.value = value;
4633                 state.easyPieChartEvent.pcent = pcent;
4634                 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4635
4636                 if(state.easyPieChartEvent.timer === null) {
4637                         state.easyPieChart_instance.disableAnimation();
4638
4639                         state.easyPieChartEvent.timer = setTimeout(function() {
4640                                 state.easyPieChartEvent.timer = null;
4641                                 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4642                         }, NETDATA.options.current.charts_selection_animation_delay);
4643                 }
4644
4645                 return true;
4646         };
4647
4648         NETDATA.easypiechartChartUpdate = function(state, data) {
4649                 var value, max, pcent;
4650
4651                 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
4652                         value = null;
4653                         max = 0;
4654                         pcent = 0;
4655                 }
4656                 else {
4657                         value = data.result[0];
4658                         max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4659                         pcent = NETDATA.percentFromValueMax(value, max);
4660                 }
4661
4662                 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4663                 state.easyPieChart_instance.update(pcent);
4664                 return true;
4665         };
4666
4667         NETDATA.easypiechartChartCreate = function(state, data) {
4668                 var self = $(state.element);
4669                 var chart = $(state.element_chart);
4670
4671                 var value = data.result[0];
4672                 var max = self.data('easypiechart-max-value') || null;
4673                 var adjust = self.data('easypiechart-adjust') || null;
4674
4675                 if(max === null) {
4676                         max = data.max;
4677                         state.easyPieChartMax = null;
4678                 }
4679                 else
4680                         state.easyPieChartMax = max;
4681
4682                 var pcent = NETDATA.percentFromValueMax(value, max);
4683
4684                 chart.data('data-percent', pcent);
4685
4686                 var size;
4687                 switch(adjust) {
4688                         case 'width': size = state.chartHeight(); break;
4689                         case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4690                         case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4691                         case 'height':
4692                         default: size = state.chartWidth(); break;
4693                 }
4694                 state.element.style.width = size + 'px';
4695                 state.element.style.height = size + 'px';
4696
4697                 var stroke = Math.floor(size / 22);
4698                 if(stroke < 3) stroke = 2;
4699
4700                 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4701                 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4702                 state.easyPieChartLabel = document.createElement('span');
4703                 state.easyPieChartLabel.className = 'easyPieChartLabel';
4704                 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4705                 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4706                 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4707                 state.element_chart.appendChild(state.easyPieChartLabel);
4708
4709                 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4710                 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4711                 state.easyPieChartTitle = document.createElement('span');
4712                 state.easyPieChartTitle.className = 'easyPieChartTitle';
4713                 state.easyPieChartTitle.innerHTML = state.title;
4714                 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4715                 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4716                 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4717                 state.element_chart.appendChild(state.easyPieChartTitle);
4718
4719                 var unitfontsize = Math.round(titlefontsize * 0.9);
4720                 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4721                 state.easyPieChartUnits = document.createElement('span');
4722                 state.easyPieChartUnits.className = 'easyPieChartUnits';
4723                 state.easyPieChartUnits.innerHTML = state.units;
4724                 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4725                 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4726                 state.element_chart.appendChild(state.easyPieChartUnits);
4727
4728                 chart.easyPieChart({
4729                         barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4730                         trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4731                         scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4732                         scaleLength: self.data('easypiechart-scalelength') || 5,
4733                         lineCap: self.data('easypiechart-linecap') || 'round',
4734                         lineWidth: self.data('easypiechart-linewidth') || stroke,
4735                         trackWidth: self.data('easypiechart-trackwidth') || undefined,
4736                         size: self.data('easypiechart-size') || size,
4737                         rotate: self.data('easypiechart-rotate') || 0,
4738                         animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4739                         easing: self.data('easypiechart-easing') || undefined
4740                 });
4741
4742                 // when we just re-create the chart
4743                 // do not animate the first update
4744                 var animate = true;
4745                 if(typeof state.easyPieChart_instance !== 'undefined')
4746                         animate = false;
4747
4748                 state.easyPieChart_instance = chart.data('easyPieChart');
4749                 if(animate === false) state.easyPieChart_instance.disableAnimation();
4750                 state.easyPieChart_instance.update(pcent);
4751                 if(animate === false) state.easyPieChart_instance.enableAnimation();
4752                 return true;
4753         };
4754
4755         // ----------------------------------------------------------------------------------------------------------------
4756         // gauge.js
4757
4758         NETDATA.gaugeInitialize = function(callback) {
4759                 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4760                         $.ajax({
4761                                 url: NETDATA.gauge_js,
4762                                 cache: true,
4763                                 dataType: "script"
4764                         })
4765                                 .done(function() {
4766                                         NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4767                                 })
4768                                 .fail(function() {
4769                                         NETDATA.chartLibraries.gauge.enabled = false;
4770                                         NETDATA.error(100, NETDATA.gauge_js);
4771                                 })
4772                                 .always(function() {
4773                                         if(typeof callback === "function")
4774                                                 callback();
4775                                 })
4776                 }
4777                 else {
4778                         NETDATA.chartLibraries.gauge.enabled = false;
4779                         if(typeof callback === "function")
4780                                 callback();
4781                 }
4782         };
4783
4784         NETDATA.gaugeAnimation = function(state, status) {
4785                 var speed = 32;
4786
4787                 if(typeof status === 'boolean' && status === false)
4788                         speed = 1000000000;
4789                 else if(typeof status === 'number')
4790                         speed = status;
4791
4792                 state.gauge_instance.animationSpeed = speed;
4793                 state.___gaugeOld__.speed = speed;
4794         }
4795
4796         NETDATA.gaugeSet = function(state, value, min, max) {
4797                 if(typeof value !== 'number') value = 0;
4798                 if(typeof min !== 'number') min = 0;
4799                 if(typeof max !== 'number') max = 0;
4800                 if(value > max) max = value;
4801                 if(value < min) min = value;
4802                 if(min > max) {
4803                         var t = min;
4804                         min = max;
4805                         max = t;
4806                 }
4807                 else if(min == max)
4808                         max = min + 1;
4809
4810                 // gauge.js has an issue if the needle
4811                 // is smaller than min or larger than max
4812                 // when we set the new values
4813                 // the needle will go crazy
4814
4815                 // to prevent it, we always feed it
4816                 // with a percentage, so that the needle
4817                 // is always between min and max
4818                 var pcent = (value - min) * 100 / (max - min);
4819
4820                 // these should never happen
4821                 if(pcent < 0) pcent = 0;
4822                 if(pcent > 100) pcent = 100;
4823
4824                 state.gauge_instance.set(pcent);
4825
4826                 state.___gaugeOld__.value = value;
4827                 state.___gaugeOld__.min = min;
4828                 state.___gaugeOld__.max = max;
4829         };
4830
4831         NETDATA.gaugeSetLabels = function(state, value, min, max) {
4832                 if(state.___gaugeOld__.valueLabel !== value) {
4833                         state.___gaugeOld__.valueLabel = value;
4834                         state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
4835                 }
4836                 if(state.___gaugeOld__.minLabel !== min) {
4837                         state.___gaugeOld__.minLabel = min;
4838                         state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
4839                 }
4840                 if(state.___gaugeOld__.maxLabel !== max) {
4841                         state.___gaugeOld__.maxLabel = max;
4842                         state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
4843                 }
4844         };
4845
4846         NETDATA.gaugeClearSelection = function(state) {
4847                 if(typeof state.gaugeEvent !== 'undefined') {
4848                         if(state.gaugeEvent.timer !== null)
4849                                 clearTimeout(state.gaugeEvent.timer);
4850
4851                         state.gaugeEvent.timer = null;
4852                 }
4853
4854                 if(state.isAutoRefreshed() === true && state.data !== null) {
4855                         NETDATA.gaugeChartUpdate(state, state.data);
4856                 }
4857                 else {
4858                         NETDATA.gaugeAnimation(state, false);
4859                         NETDATA.gaugeSet(state, null, null, null);
4860                         NETDATA.gaugeSetLabels(state, null, null, null);
4861                 }
4862
4863                 NETDATA.gaugeAnimation(state, true);
4864                 return true;
4865         };
4866
4867         NETDATA.gaugeSetSelection = function(state, t) {
4868                 if(state.timeIsVisible(t) !== true)
4869                         return NETDATA.gaugeClearSelection(state);
4870
4871                 var slot = state.calculateRowForTime(t);
4872                 if(slot < 0 || slot >= state.data.result.length)
4873                         return NETDATA.gaugeClearSelection(state);
4874
4875                 if(typeof state.gaugeEvent === 'undefined') {
4876                         state.gaugeEvent = {
4877                                 timer: null,
4878                                 value: 0,
4879                                 min: 0,
4880                                 max: 0
4881                         };
4882                 }
4883
4884                 var value = state.data.result[state.data.result.length - 1 - slot];
4885                 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
4886                 var min = 0;
4887
4888                 state.gaugeEvent.value = value;
4889                 state.gaugeEvent.max = max;
4890                 state.gaugeEvent.min = min;
4891                 NETDATA.gaugeSetLabels(state, value, min, max);
4892
4893                 if(state.gaugeEvent.timer === null) {
4894                         NETDATA.gaugeAnimation(state, false);
4895
4896                         state.gaugeEvent.timer = setTimeout(function() {
4897                                 state.gaugeEvent.timer = null;
4898                                 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
4899                         }, NETDATA.options.current.charts_selection_animation_delay);
4900                 }
4901
4902                 return true;
4903         };
4904
4905         NETDATA.gaugeChartUpdate = function(state, data) {
4906                 var value, min, max;
4907
4908                 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
4909                         value = 0;
4910                         min = 0;
4911                         max = 1;
4912                         NETDATA.gaugeSetLabels(state, null, null, null);
4913                 }
4914                 else {
4915                         value = data.result[0];
4916                         min = 0;
4917                         max = (state.gaugeMax === null)?data.max:state.gaugeMax;
4918                         if(value > max) max = value;
4919                         NETDATA.gaugeSetLabels(state, value, min, max);
4920                 }
4921
4922                 NETDATA.gaugeSet(state, value, min, max);
4923                 return true;
4924         };
4925
4926         NETDATA.gaugeChartCreate = function(state, data) {
4927                 var self = $(state.element);
4928                 var chart = $(state.element_chart);
4929
4930                 var value = data.result[0];
4931                 var max = self.data('gauge-max-value') || null;
4932                 var adjust = self.data('gauge-adjust') || null;
4933                 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
4934                 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
4935                 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
4936                 var stopColor = self.data('gauge-stop-color') || void 0;
4937                 var generateGradient = self.data('gauge-generate-gradient') || false;
4938
4939                 if(max === null) {
4940                         max = data.max;
4941                         state.gaugeMax = null;
4942                 }
4943                 else
4944                         state.gaugeMax = max;
4945
4946                 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
4947                 //switch(adjust) {
4948                 //      case 'width': width = height * ratio; break;
4949                 //      case 'height':
4950                 //      default: height = width / ratio; break;
4951                 //}
4952                 //state.element.style.width = width.toString() + 'px';
4953                 //state.element.style.height = height.toString() + 'px';
4954
4955                 var lum_d = 0.05;
4956
4957                 var options = {
4958                         lines: 12,                                      // The number of lines to draw
4959                         angle: 0.15,                            // The length of each line
4960                         lineWidth: 0.44,                        // 0.44 The line thickness
4961                         pointer: {
4962                                 length: 0.8,                    // 0.9 The radius of the inner circle
4963                                 strokeWidth: 0.035,             // The rotation offset
4964                                 color: pointerColor             // Fill color
4965                         },
4966                         colorStart: startColor,         // Colors
4967                         colorStop: stopColor,           // just experiment with them
4968                         strokeColor: strokeColor,       // to see which ones work best for you
4969                         limitMax: true,
4970                         generateGradient: generateGradient,
4971                         gradientType: 0
4972                 };
4973
4974                 if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
4975                         options.percentColors = [
4976                                 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
4977                                 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
4978                                 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
4979                                 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
4980                                 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
4981                                 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
4982                                 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
4983                                 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
4984                                 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
4985                                 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
4986                                 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
4987                 }
4988
4989                 state.gauge_canvas = document.createElement('canvas');
4990                 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
4991                 state.gauge_canvas.className = 'gaugeChart';
4992                 state.gauge_canvas.width  = width;
4993                 state.gauge_canvas.height = height;
4994                 state.element_chart.appendChild(state.gauge_canvas);
4995
4996                 var valuefontsize = Math.floor(height / 6);
4997                 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
4998                 state.gaugeChartLabel = document.createElement('span');
4999                 state.gaugeChartLabel.className = 'gaugeChartLabel';
5000                 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5001                 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5002                 state.element_chart.appendChild(state.gaugeChartLabel);
5003
5004                 var titlefontsize = Math.round(valuefontsize / 2);
5005                 var titletop = 0;
5006                 state.gaugeChartTitle = document.createElement('span');
5007                 state.gaugeChartTitle.className = 'gaugeChartTitle';
5008                 state.gaugeChartTitle.innerHTML = state.title;
5009                 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5010                 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5011                 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5012                 state.element_chart.appendChild(state.gaugeChartTitle);
5013
5014                 var unitfontsize = Math.round(titlefontsize * 0.9);
5015                 state.gaugeChartUnits = document.createElement('span');
5016                 state.gaugeChartUnits.className = 'gaugeChartUnits';
5017                 state.gaugeChartUnits.innerHTML = state.units;
5018                 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5019                 state.element_chart.appendChild(state.gaugeChartUnits);
5020
5021                 state.gaugeChartMin = document.createElement('span');
5022                 state.gaugeChartMin.className = 'gaugeChartMin';
5023                 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5024                 state.element_chart.appendChild(state.gaugeChartMin);
5025
5026                 state.gaugeChartMax = document.createElement('span');
5027                 state.gaugeChartMax.className = 'gaugeChartMax';
5028                 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5029                 state.element_chart.appendChild(state.gaugeChartMax);
5030
5031                 // when we just re-create the chart
5032                 // do not animate the first update
5033                 var animate = true;
5034                 if(typeof state.gauge_instance !== 'undefined')
5035                         animate = false;
5036
5037                 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5038
5039                 state.___gaugeOld__ = {
5040                         value: value,
5041                         min: 0,
5042                         max: max,
5043                         valueLabel: null,
5044                         minLabel: null,
5045                         maxLabel: null
5046                 };
5047
5048                 // we will always feed a percentage
5049                 state.gauge_instance.minValue = 0;
5050                 state.gauge_instance.maxValue = 100;
5051
5052                 NETDATA.gaugeAnimation(state, animate);
5053                 NETDATA.gaugeSet(state, value, 0, max);
5054                 NETDATA.gaugeSetLabels(state, value, 0, max);
5055                 NETDATA.gaugeAnimation(state, true);
5056                 return true;
5057         };
5058
5059         // ----------------------------------------------------------------------------------------------------------------
5060         // Charts Libraries Registration
5061
5062         NETDATA.chartLibraries = {
5063                 "dygraph": {
5064                         initialize: NETDATA.dygraphInitialize,
5065                         create: NETDATA.dygraphChartCreate,
5066                         update: NETDATA.dygraphChartUpdate,
5067                         resize: function(state) {
5068                                 if(typeof state.dygraph_instance.resize === 'function')
5069                                         state.dygraph_instance.resize();
5070                         },
5071                         setSelection: NETDATA.dygraphSetSelection,
5072                         clearSelection:  NETDATA.dygraphClearSelection,
5073                         toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5074                         initialized: false,
5075                         enabled: true,
5076                         format: function(state) { return 'json'; },
5077                         options: function(state) { return 'ms|flip'; },
5078                         legend: function(state) {
5079                                 if(this.isSparkline(state) === false)
5080                                         return 'right-side';
5081                                 else
5082                                         return null;
5083                         },
5084                         autoresize: function(state) { return true; },
5085                         max_updates_to_recreate: function(state) { return 5000; },
5086                         track_colors: function(state) { return true; },
5087                         pixels_per_point: function(state) {
5088                                 if(this.isSparkline(state) === false)
5089                                         return 3;
5090                                 else
5091                                         return 2;
5092                         },
5093
5094                         isSparkline: function(state) {
5095                                 if(typeof state.dygraph_sparkline === 'undefined') {
5096                                         var t = $(state.element).data('dygraph-theme');
5097                                         if(t === 'sparkline')
5098                                                 state.dygraph_sparkline = true;
5099                                         else
5100                                                 state.dygraph_sparkline = false;
5101                                 }
5102                                 return state.dygraph_sparkline;
5103                         }
5104                 },
5105                 "sparkline": {
5106                         initialize: NETDATA.sparklineInitialize,
5107                         create: NETDATA.sparklineChartCreate,
5108                         update: NETDATA.sparklineChartUpdate,
5109                         resize: null,
5110                         setSelection: undefined, // function(state, t) { return true; },
5111                         clearSelection: undefined, // function(state) { return true; },
5112                         toolboxPanAndZoom: null,
5113                         initialized: false,
5114                         enabled: true,
5115                         format: function(state) { return 'array'; },
5116                         options: function(state) { return 'flip|abs'; },
5117                         legend: function(state) { return null; },
5118                         autoresize: function(state) { return false; },
5119                         max_updates_to_recreate: function(state) { return 5000; },
5120                         track_colors: function(state) { return false; },
5121                         pixels_per_point: function(state) { return 3; }
5122                 },
5123                 "peity": {
5124                         initialize: NETDATA.peityInitialize,
5125                         create: NETDATA.peityChartCreate,
5126                         update: NETDATA.peityChartUpdate,
5127                         resize: null,
5128                         setSelection: undefined, // function(state, t) { return true; },
5129                         clearSelection: undefined, // function(state) { return true; },
5130                         toolboxPanAndZoom: null,
5131                         initialized: false,
5132                         enabled: true,
5133                         format: function(state) { return 'ssvcomma'; },
5134                         options: function(state) { return 'null2zero|flip|abs'; },
5135                         legend: function(state) { return null; },
5136                         autoresize: function(state) { return false; },
5137                         max_updates_to_recreate: function(state) { return 5000; },
5138                         track_colors: function(state) { return false; },
5139                         pixels_per_point: function(state) { return 3; }
5140                 },
5141                 "morris": {
5142                         initialize: NETDATA.morrisInitialize,
5143                         create: NETDATA.morrisChartCreate,
5144                         update: NETDATA.morrisChartUpdate,
5145                         resize: null,
5146                         setSelection: undefined, // function(state, t) { return true; },
5147                         clearSelection: undefined, // function(state) { return true; },
5148                         toolboxPanAndZoom: null,
5149                         initialized: false,
5150                         enabled: true,
5151                         format: function(state) { return 'json'; },
5152                         options: function(state) { return 'objectrows|ms'; },
5153                         legend: function(state) { return null; },
5154                         autoresize: function(state) { return false; },
5155                         max_updates_to_recreate: function(state) { return 50; },
5156                         track_colors: function(state) { return false; },
5157                         pixels_per_point: function(state) { return 15; }
5158                 },
5159                 "google": {
5160                         initialize: NETDATA.googleInitialize,
5161                         create: NETDATA.googleChartCreate,
5162                         update: NETDATA.googleChartUpdate,
5163                         resize: null,
5164                         setSelection: undefined, //function(state, t) { return true; },
5165                         clearSelection: undefined, //function(state) { return true; },
5166                         toolboxPanAndZoom: null,
5167                         initialized: false,
5168                         enabled: true,
5169                         format: function(state) { return 'datatable'; },
5170                         options: function(state) { return ''; },
5171                         legend: function(state) { return null; },
5172                         autoresize: function(state) { return false; },
5173                         max_updates_to_recreate: function(state) { return 300; },
5174                         track_colors: function(state) { return false; },
5175                         pixels_per_point: function(state) { return 4; }
5176                 },
5177                 "raphael": {
5178                         initialize: NETDATA.raphaelInitialize,
5179                         create: NETDATA.raphaelChartCreate,
5180                         update: NETDATA.raphaelChartUpdate,
5181                         resize: null,
5182                         setSelection: undefined, // function(state, t) { return true; },
5183                         clearSelection: undefined, // function(state) { return true; },
5184                         toolboxPanAndZoom: null,
5185                         initialized: false,
5186                         enabled: true,
5187                         format: function(state) { return 'json'; },
5188                         options: function(state) { return ''; },
5189                         legend: function(state) { return null; },
5190                         autoresize: function(state) { return false; },
5191                         max_updates_to_recreate: function(state) { return 5000; },
5192                         track_colors: function(state) { return false; },
5193                         pixels_per_point: function(state) { return 3; }
5194                 },
5195                 "c3": {
5196                         initialize: NETDATA.c3Initialize,
5197                         create: NETDATA.c3ChartCreate,
5198                         update: NETDATA.c3ChartUpdate,
5199                         resize: null,
5200                         setSelection: undefined, // function(state, t) { return true; },
5201                         clearSelection: undefined, // function(state) { return true; },
5202                         toolboxPanAndZoom: null,
5203                         initialized: false,
5204                         enabled: true,
5205                         format: function(state) { return 'csvjsonarray'; },
5206                         options: function(state) { return 'milliseconds'; },
5207                         legend: function(state) { return null; },
5208                         autoresize: function(state) { return false; },
5209                         max_updates_to_recreate: function(state) { return 5000; },
5210                         track_colors: function(state) { return false; },
5211                         pixels_per_point: function(state) { return 15; }
5212                 },
5213                 "d3": {
5214                         initialize: NETDATA.d3Initialize,
5215                         create: NETDATA.d3ChartCreate,
5216                         update: NETDATA.d3ChartUpdate,
5217                         resize: null,
5218                         setSelection: undefined, // function(state, t) { return true; },
5219                         clearSelection: undefined, // function(state) { return true; },
5220                         toolboxPanAndZoom: null,
5221                         initialized: false,
5222                         enabled: true,
5223                         format: function(state) { return 'json'; },
5224                         options: function(state) { return ''; },
5225                         legend: function(state) { return null; },
5226                         autoresize: function(state) { return false; },
5227                         max_updates_to_recreate: function(state) { return 5000; },
5228                         track_colors: function(state) { return false; },
5229                         pixels_per_point: function(state) { return 3; }
5230                 },
5231                 "easypiechart": {
5232                         initialize: NETDATA.easypiechartInitialize,
5233                         create: NETDATA.easypiechartChartCreate,
5234                         update: NETDATA.easypiechartChartUpdate,
5235                         resize: null,
5236                         setSelection: NETDATA.easypiechartSetSelection,
5237                         clearSelection: NETDATA.easypiechartClearSelection,
5238                         toolboxPanAndZoom: null,
5239                         initialized: false,
5240                         enabled: true,
5241                         format: function(state) { return 'array'; },
5242                         options: function(state) { return 'absolute'; },
5243                         legend: function(state) { return null; },
5244                         autoresize: function(state) { return false; },
5245                         max_updates_to_recreate: function(state) { return 5000; },
5246                         track_colors: function(state) { return true; },
5247                         pixels_per_point: function(state) { return 3; },
5248                         aspect_ratio: 100
5249                 },
5250                 "gauge": {
5251                         initialize: NETDATA.gaugeInitialize,
5252                         create: NETDATA.gaugeChartCreate,
5253                         update: NETDATA.gaugeChartUpdate,
5254                         resize: null,
5255                         setSelection: NETDATA.gaugeSetSelection,
5256                         clearSelection: NETDATA.gaugeClearSelection,
5257                         toolboxPanAndZoom: null,
5258                         initialized: false,
5259                         enabled: true,
5260                         format: function(state) { return 'array'; },
5261                         options: function(state) { return 'absolute'; },
5262                         legend: function(state) { return null; },
5263                         autoresize: function(state) { return false; },
5264                         max_updates_to_recreate: function(state) { return 5000; },
5265                         track_colors: function(state) { return true; },
5266                         pixels_per_point: function(state) { return 3; },
5267                         aspect_ratio: 70
5268                 }
5269         };
5270
5271         NETDATA.registerChartLibrary = function(library, url) {
5272                 if(NETDATA.options.debug.libraries === true)
5273                         console.log("registering chart library: " + library);
5274
5275                 NETDATA.chartLibraries[library].url = url;
5276                 NETDATA.chartLibraries[library].initialized = true;
5277                 NETDATA.chartLibraries[library].enabled = true;
5278         }
5279
5280         // ----------------------------------------------------------------------------------------------------------------
5281         // Start up
5282
5283         NETDATA.requiredJs = [
5284                 {
5285                         url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5286                         isAlreadyLoaded: function() {
5287                                 if(typeof $().emulateTransitionEnd == 'function')
5288                                         return true;
5289                                 else {
5290                                         if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5291                                                 return true;
5292                                         else
5293                                                 return false;
5294                                 }
5295                         }
5296                 },
5297                 {
5298                         url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5299                         isAlreadyLoaded: function() { return false; }
5300                 },
5301                 {
5302                         url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5303                         isAlreadyLoaded: function() { return false; }
5304                 }
5305         ];
5306
5307         NETDATA.requiredCSS = [
5308                 {
5309                         url: NETDATA.themes.current.bootstrap_css,
5310                         isAlreadyLoaded: function() {
5311                                 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5312                                         return true;
5313                                 else
5314                                         return false;
5315                         }
5316                 },
5317                 {
5318                         url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5319                         isAlreadyLoaded: function() { return false; }
5320                 },
5321                 {
5322                         url: NETDATA.themes.current.dashboard_css,
5323                         isAlreadyLoaded: function() { return false; }
5324                 },
5325                 {
5326                         url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5327                         isAlreadyLoaded: function() { return false; }
5328                 }
5329         ];
5330
5331         NETDATA.loadRequiredJs = function(index, callback) {
5332                 if(index >= NETDATA.requiredJs.length)  {
5333                         if(typeof callback === 'function')
5334                                 callback();
5335                         return;
5336                 }
5337
5338                 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5339                         NETDATA.loadRequiredJs(++index, callback);
5340                         return;
5341                 }
5342
5343                 if(NETDATA.options.debug.main_loop === true)
5344                         console.log('loading ' + NETDATA.requiredJs[index].url);
5345
5346                 $.ajax({
5347                         url: NETDATA.requiredJs[index].url,
5348                         cache: true,
5349                         dataType: "script"
5350                 })
5351                 .success(function() {
5352                         if(NETDATA.options.debug.main_loop === true)
5353                                 console.log('loaded ' + NETDATA.requiredJs[index].url);
5354
5355                         NETDATA.loadRequiredJs(++index, callback);
5356                 })
5357                 .fail(function() {
5358                         alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5359                 })
5360         }
5361
5362         NETDATA.loadRequiredCSS = function(index) {
5363                 if(index >= NETDATA.requiredCSS.length)
5364                         return;
5365
5366                 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5367                         NETDATA.loadRequiredCSS(++index);
5368                         return;
5369                 }
5370
5371                 if(NETDATA.options.debug.main_loop === true)
5372                         console.log('loading ' + NETDATA.requiredCSS[index].url);
5373
5374                 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5375                 NETDATA.loadRequiredCSS(++index);
5376         }
5377
5378         NETDATA.errorReset();
5379         NETDATA.loadRequiredCSS(0);
5380
5381         NETDATA._loadjQuery(function() {
5382                 NETDATA.loadRequiredJs(0, function() {
5383                         if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
5384                                 if(NETDATA.options.debug.main_loop === true)
5385                                         console.log('starting chart refresh thread');
5386
5387                                 NETDATA.start();
5388                         }
5389                 });
5390         });
5391
5392         // window.NETDATA = NETDATA;
5393 // })(window, document);