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