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