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