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