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