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