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