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