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