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