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