1 // You can set the following variables before loading this script:
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
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
17 // fix IE bug with console
18 if(!window.console){ window.console = {log: function(){} }; }
21 var NETDATA = window.NETDATA || {};
23 // ----------------------------------------------------------------------------------------------------------------
24 // Detect the netdata server
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;
31 if(typeof document.currentScript !== 'undefined') {
32 script = document.currentScript;
35 var all_scripts = document.getElementsByTagName('script');
36 script = all_scripts[all_scripts.length - 1];
39 if (typeof script.getAttribute.length !== 'undefined')
42 script = script.getAttribute('src', -1);
44 var link = document.createElement('a');
45 link.setAttribute('href', script);
47 if(!link.protocol || !link.hostname) return null;
50 if(base) base += "//";
51 base += link.hostname;
53 if(link.port) base += ":" + link.port;
59 if(typeof netdataServer !== 'undefined')
60 NETDATA.serverDefault = netdataServer;
62 NETDATA.serverDefault = NETDATA._scriptSource();
64 if(NETDATA.serverDefault.slice(-1) !== '/')
65 NETDATA.serverDefault += '/';
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';
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' ];
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' ];
94 // ----------------------------------------------------------------------------------------------------------------
95 // the defaults for all charts
97 // if the user does not specify any of these, the following will be used
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
112 // ----------------------------------------------------------------------------------------------------------------
116 targets: null, // an array of all the DOM elements that are
117 // currently visible (independently of their
118 // viewport visibility)
120 updated_dom: true, // when true, the DOM has been updated with
121 // new elements we have to check.
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
127 page_is_visible: true, // when true, this page is visible
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
136 last_resized: 0, // the timestamp of the last resize request
138 // the current profile
139 // we may have many...
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
149 idle_between_charts: 50, // ms - how much time to wait between chart updates
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.
157 idle_between_loops: 200, // ms - if all charts have been updated, wait this
158 // time before starting again.
160 idle_lost_focus: 500, // ms - when the window does not have focus, check
161 // if focus has been regained, every this time
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
167 sync_selection_delay: 2500, // ms - when you pan or zoom a chart, wait this amount
168 // of time before setting up synchronized selections
171 sync_selection: true, // enable or disable selection sync
173 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
175 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
177 update_only_visible: true, // enable or disable visibility management
179 parallel_refresher: true, // enable parallel refresh of charts
181 color_fill_opacity: {
202 if(NETDATA.options.debug.main_loop) console.log('welcome to NETDATA');
204 window.onresize = function(event) {
205 NETDATA.options.last_resized = new Date().getTime();
208 // ----------------------------------------------------------------------------------------------------------------
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 }
218 NETDATA.errorLast = {
224 NETDATA.error = function(code, msg) {
225 NETDATA.errorLast.code = code;
226 NETDATA.errorLast.message = msg;
227 NETDATA.errorLast.datetime = new Date().getTime();
229 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
231 if(NETDATA.errorCodes[code].alert)
232 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
235 NETDATA.errorReset = function() {
236 NETDATA.errorLast.code = 0;
237 NETDATA.errorLast.message = "You are doing fine!";
238 NETDATA.errorLast.datetime = 0;
241 // ----------------------------------------------------------------------------------------------------------------
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.
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.
251 NETDATA.chartRegistry = {
254 add: function(host, id, data) {
255 host = host.replace(/:/g, "_").replace(/\//g, "_");
256 id = id.replace(/:/g, "_").replace(/\//g, "_");
258 if(typeof this.charts[host] === 'undefined')
259 this.charts[host] = {};
261 this.charts[host][id] = data;
264 get: function(host, id) {
265 host = host.replace(/:/g, "_").replace(/\//g, "_");
266 id = id.replace(/:/g, "_").replace(/\//g, "_");
268 if(typeof this.charts[host] === 'undefined')
271 if(typeof this.charts[host][id] === 'undefined')
274 return this.charts[host][id];
278 // ----------------------------------------------------------------------------------------------------------------
279 // Global Pan and Zoom on charts
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.
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
293 master: null, // the master chart (state), to which all others
296 force_before_ms: null, // the timespan to sync all other charts
297 force_after_ms: null,
300 setMaster: function(state, after, before) {
301 if(!NETDATA.options.current.sync_pan_and_zoom) return;
303 if(this.master !== null && this.master !== state)
304 this.master.resetChart();
306 var now = new Date().getTime();
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;
315 clearMaster: function() {
316 if(!NETDATA.options.current.sync_pan_and_zoom) return;
318 if(this.master !== null) {
319 var state = this.master;
320 this.master = null; // prevent infinite recursion
323 NETDATA.options.auto_refresher_stop_until = 0;
328 this.force_after_ms = null;
329 this.force_before_ms = null;
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;
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;
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)
351 if(state.needsResize())
354 if(state.follows_global === this.seq)
361 // ----------------------------------------------------------------------------------------------------------------
362 // Our state object, where all per-chart values are stored
364 chartState = function(element) {
368 uuid: NETDATA.guid(), // GUID - a unique identifier for the chart
369 id: self.data('netdata'), // string - the name of chart
371 // the user given dimensions of the element
372 width: self.data('width') || NETDATA.chartDefaults.width,
373 height: self.data('height') || NETDATA.chartDefaults.height,
375 // string - the netdata server URL, without any path
376 host: self.data('host') || NETDATA.chartDefaults.host,
378 // string - the grouping method requested by the user
379 method: self.data('method') || NETDATA.chartDefaults.method,
381 // the time-range requested by the user
382 after: self.data('after') || NETDATA.chartDefaults.after,
383 before: self.data('before') || NETDATA.chartDefaults.before,
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,
389 // the dimensions requested by the user
390 dimensions: self.data('dimensions') || null,
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
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: {
409 chart_url: null, // string - the url to download chart info
410 chart: null, // object - the chart as downloaded from the server
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
420 updates_counter: 0, // numeric - the number of refreshes made so far
421 updates_since_last_creation: 0,
423 follows_global: 0, // the sequence number of the global synchronization
425 // Used with NETDATA.globalPanAndZoom.seq
427 last_resized: 0, // the last time the chart was resized
429 mode: null, // auto, pan, zoom
430 // this is a pointer to one of the sub-classes below
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,
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,
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,
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
493 // ----------------------------------------------------------------------------------------------------------------
494 // global selection sync
496 NETDATA.globalSelectionSync = {
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;
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;
515 // this chart is the master of the global selection sync
516 chartState.prototype.globalSelectionSyncBeMaster = function() {
518 if(NETDATA.globalSelectionSync.state === this) {
519 if(this.debug) this.log('sync: I am the master already.');
523 if(NETDATA.globalSelectionSync.state) {
524 if(this.debug) this.log('sync: I am not the sync master. Resetting global sync.');
525 this.globalSelectionSyncStop();
529 if(this.debug) this.log('sync: becoming sync master.');
530 this.selected = true;
531 NETDATA.globalSelectionSync.state = this;
533 // find the all slaves
535 $.each(NETDATA.options.targets, function(i, target) {
536 var st = NETDATA.chartState(target);
538 if(self.debug) st.log('sync: not adding me to sync');
540 else if(st.globalSelectionSyncIsEligible()) {
541 if(self.debug) st.log('sync: adding to sync as slave');
542 st.globalSelectionSyncBeSlave();
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())
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);
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?).');
568 if(this.debug) this.log('sync: trying to be sync master.');
569 this.globalSelectionSyncBeMaster();
572 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
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');
579 if(self.debug) st.log('sync: showing master selection');
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...');
590 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
592 if(self.debug) st.log('sync: not adding me to sync stop');
595 if(self.debug) st.log('sync: removed slave from sync');
600 NETDATA.globalSelectionSync.slaves = [];
601 NETDATA.globalSelectionSync.state = null;
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;
609 chartState.prototype.setSelection = function(t) {
610 if(typeof this.library.setSelection === 'function') {
611 if(this.library.setSelection(this, t))
612 this.selected = true;
614 this.selected = false;
616 else this.selected = true;
618 if(this.selected && this.debug) this.log('selection set to ' + t.toString());
620 return this.selected;
623 chartState.prototype.clearSelection = function() {
625 if(typeof this.library.clearSelection === 'function') {
626 if(this.library.clearSelection(this))
627 this.selected = false;
629 this.selected = true;
631 else this.selected = false;
633 if(!this.selected && this.debug) this.log('selection cleared');
637 return this.selected;
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)
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);
652 // ----------------------------------------------------------------------------------------------------------------
655 chartState.prototype.log = function(msg) {
656 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
659 chartState.prototype.pauseChart = function() {
661 if(this.debug) this.log('paused');
666 chartState.prototype.unpauseChart = function() {
668 if(this.debug) this.log('unpaused');
673 chartState.prototype.resetChart = function() {
674 NETDATA.globalPanAndZoom.clearMaster();
675 this.follows_global = 0;
677 this.clearSelection();
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;
685 this.selected = false;
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
693 if(!NETDATA.options.current.sync_pan_and_zoom)
697 chartState.prototype.setMode = function(m) {
699 if(this.current.name === m) return;
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;
715 this.current = this.auto;
717 this.current = this.pan;
718 else if(m === 'zoom')
719 this.current = this.zoom;
721 this.current = this.auto;
723 this.current.force_update_at = 0;
724 this.current.force_before_ms = null;
725 this.current.force_after_ms = null;
727 if(this.debug) this.log('mode set to ' + this.current.name);
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;
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;
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);
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');
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');
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');
760 chartState.prototype.updateChartPanOrZoom = function(after, before) {
763 if(this.current.name === 'auto') {
764 if(this.debug) this.log('updateChartPanOrZoom(): caller did not set proper mode');
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');
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());
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());
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')
794 if(this.debug) this.log('updateChartPanOrZoom(' + (after / 1000).toString() + ' - ' + (before / 1000).toString() + '): IGNORE');
798 chartState.prototype.legendFormatValue = function(value) {
799 if(typeof value !== 'number' || value === null) return '';
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();
807 chartState.prototype.legendSetLabelValue = function(label, string) {
808 if(typeof this.element_legend_childs.series[label] === 'undefined')
811 if(this.element_legend_childs.series[label].value !== null)
812 this.element_legend_childs.series[label].value.innerHTML = string;
814 if(this.element_legend_childs.series[label].user !== null)
815 this.element_legend_childs.series[label].user.innerHTML = string;
818 chartState.prototype.legendSetDate = function(ms) {
819 if(typeof ms !== 'number') {
820 this.legendUndefined();
824 var d = new Date(ms);
826 if(this.element_legend_childs.title_date)
827 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
829 if(this.element_legend_childs.title_time)
830 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
832 if(this.element_legend_childs.title_units)
833 this.element_legend_childs.title_units.innerHTML = this.chart.units;
836 chartState.prototype.legendUndefined = function() {
837 if(this.element_legend_childs.title_date)
838 this.element_legend_childs.title_date.innerHTML = ' ';
840 if(this.element_legend_childs.title_time)
841 this.element_legend_childs.title_time.innerHTML = this.chart.name;
843 if(this.element_legend_childs.title_units)
844 this.element_legend_childs.title_units.innerHTML = ' ';
847 chartState.prototype.legendShowLatestValues = function() {
848 if(!this.chart) return;
849 if(this.selected) return;
851 if(!this.current.data) {
852 this.legendUndefined();
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);
859 this.legendUndefined();
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')
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]));
868 this.legendSetLabelValue(this.current.data.dimension_names[i], '');
872 chartState.prototype.legendReset = function() {
873 this.legendShowLatestValues();
876 chartState.prototype.legendUpdateDOM = function() {
877 if(!this.hasLegend()) return;
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');
886 else if(!this.current.data) {
887 // this.log('the chart does not have any data - requesting legend update');
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]);
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]));
907 if(this.debug) this.log('updating Legend DOM');
909 this.element_legend.innerHTML = '';
911 this.element_legend_childs = {
912 title_date: document.createElement('span'),
913 title_time: document.createElement('span'),
914 title_units: document.createElement('span'),
918 this.element_legend_childs.title_date.className += "netdata-legend-title-date";
919 this.element_legend.appendChild(this.element_legend_childs.title_date);
921 this.element_legend.appendChild(document.createElement('br'));
923 this.element_legend_childs.title_time.className += "netdata-legend-title-time";
924 this.element_legend.appendChild(this.element_legend_childs.title_time);
926 this.element_legend.appendChild(document.createElement('br'));
928 this.element_legend_childs.title_units.className += "netdata-legend-title-units";
929 this.element_legend.appendChild(this.element_legend_childs.title_units);
931 this.element_legend.appendChild(document.createElement('br'));
933 var nano = document.createElement('div');
934 nano.className = 'netdata-legend-series';
935 this.element_legend.appendChild(nano);
937 var content = document.createElement('div');
938 content.className = 'netdata-legend-series-content';
939 nano.appendChild(content);
942 var genLabel = function(state, parent, name, count) {
943 var c = count % NETDATA.colors.length;
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);
949 state.element_legend_childs.series[name] = {
950 name: document.createElement('span'),
951 value: document.createElement('span'),
955 var label = state.element_legend_childs.series[name];
957 label.name.className += 'netdata-legend-name';
958 label.value.className += 'netdata-legend-value';
959 label.value.title = name;
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;
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] + ')';
968 var text = document.createTextNode(' ' + name);
970 label.name.appendChild(table);
971 label.name.appendChild(text);
973 label.name.style.color = NETDATA.colors[c];
974 label.value.style.color = NETDATA.colors[c];
977 parent.appendChild(document.createElement('br'));
979 parent.appendChild(label.name);
980 parent.appendChild(label.value);
983 if(this.current.data) {
985 $.each(me.current.data.dimension_names, function(i, d) {
986 genLabel(me, content, d, i);
991 $.each(me.chart.dimensions, function(i, d) {
992 genLabel(me, content, d.name, i);
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';
1002 this.element_legend_childs.hidden = document.createElement('div');
1003 el.appendChild(this.element_legend_childs.hidden);
1004 nano.appendChild(el);
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'
1015 this.legendShowLatestValues();
1018 chartState.prototype.createChartDOM = function() {
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';
1025 html += '<div class="netdata-chart-with-legend-right netdata-'
1026 + this.library_name + '-chart-with-legend-right" id="'
1027 + this.element_chart_id
1030 html += '<div class="netdata-chart-legend netdata-'
1031 + this.library_name + '-legend" id="'
1032 + this.element_legend_id
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
1043 this.element.innerHTML = html;
1044 this.element_chart = document.getElementById(this.element_chart_id);
1045 $(this.element_chart).data('netdata-state-object', this);
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();
1054 chartState.prototype.hasLegend = function() {
1055 if(this.element_legend) return true;
1057 if(this.library && this.library.legend(this) === 'right-side') {
1058 var legend = $(this.element).data('legend') || 'yes';
1059 if(legend === 'no') return false;
1066 chartState.prototype.legendWidth = function() {
1067 return (this.hasLegend())?110:0;
1070 chartState.prototype.legendHeight = function() {
1071 return $(this.element).height();
1074 chartState.prototype.chartWidth = function() {
1075 return $(this.element).width() - this.legendWidth();
1078 chartState.prototype.chartHeight = function() {
1079 return $(this.element).height();
1082 chartState.prototype.chartPixelsPerPoint = function() {
1083 // force an options provided detail
1084 var px = this.pixels_per_point;
1086 if(this.library && px < this.library.pixels_per_point(this))
1087 px = this.library.pixels_per_point(this);
1089 if(px < NETDATA.options.current.pixels_per_point)
1090 px = NETDATA.options.current.pixels_per_point;
1095 chartState.prototype.needsResize = function() {
1096 return (this.library && !this.library.autoresize() && this.last_resized < NETDATA.options.last_resized);
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();
1107 chartState.prototype.chartURL = function() {
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;
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;
1121 this.current.requested_after_ms = after * 1000;
1122 this.current.requested_before_ms = before * 1000;
1124 this.current.points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
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';
1135 this.current.url += "&after=" + after.toString();
1138 this.current.url += "&before=" + before.toString();
1141 this.current.url += "&dimensions=" + this.dimensions;
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);
1146 chartState.prototype.updateChartWithData = function(data) {
1147 if(this.debug) this.log('got data from netdata server');
1148 this.current.data = data;
1150 var started = new Date().getTime();
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;
1157 if(typeof data.after !== 'undefined')
1158 this.current.after_ms = data.after * 1000;
1160 if(typeof data.before !== 'undefined')
1161 this.current.before_ms = data.before * 1000;
1163 if(typeof data.first_entry_t !== 'undefined')
1164 this.current.first_entry_ms = data.first_entry_t * 1000;
1166 if(typeof data.last_entry_t !== 'undefined')
1167 this.current.last_entry_ms = data.last_entry_t * 1000;
1169 if(typeof data.points !== 'undefined')
1170 this.current.points = data.points;
1175 this.updates_counter++;
1178 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
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());
1183 this.log('STATUS: forced: unset');
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());
1190 // this may force the chart to be re-created
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;
1198 if(this.created_ms && typeof this.library.update === 'function') {
1199 if(this.debug) this.log('updating chart...');
1201 // check and update the legend
1202 this.legendUpdateDOM();
1204 this.updates_since_last_creation++;
1205 if(NETDATA.options.debug.chart_errors) {
1206 this.library.update(this, data);
1210 this.library.update(this, data);
1213 this.error('chart "' + this.id + '" failed to be updated as ' + this.library_name);
1218 if(this.debug) this.log('creating chart...');
1220 this.createChartDOM();
1221 this.updates_since_last_creation = 0;
1223 if(NETDATA.options.debug.chart_errors) {
1224 this.library.create(this, data);
1225 this.created_ms = new Date().getTime();
1229 this.library.create(this, data);
1230 this.created_ms = new Date().getTime();
1233 this.error('chart "' + this.id + '" failed to be created as ' + this.library_name);
1237 this.legendShowLatestValues();
1239 // update the performance counters
1240 var now = new Date().getTime();
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;
1247 this.current.last_autorefreshed = now;
1249 this.refresh_dt_ms = now - started;
1250 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
1252 if(this.refresh_dt_element)
1253 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
1256 chartState.prototype.updateChart = function(callback) {
1259 this.getChart(function() { self.updateChart(callback); });
1263 if(!this.library.initialized) {
1265 this.library.initialize(function() { self.updateChart(callback); });
1269 this.clearSelection();
1271 if(this.debug) this.log('updating from ' + this.current.url);
1274 this.xhr = $.ajax( {
1275 url: this.current.url,
1280 .success(function(data) {
1281 if(self.debug) self.log('data received. updating chart.');
1282 self.updateChartWithData(data);
1285 self.error('data download failed for url: ' + self.current.url);
1287 .always(function() {
1288 if(typeof callback === 'function') callback();
1292 chartState.prototype.isVisible = function() {
1293 if(NETDATA.options.current.update_only_visible)
1294 return $(this.element).visible(true);
1299 chartState.prototype.isAutoRefreshed = function() {
1300 return (this.current.autorefresh);
1303 chartState.prototype.canBeAutoRefreshed = function() {
1304 now = new Date().getTime();
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');
1313 if(this.debug) this.log('I am not enabled');
1317 if(!this.isVisible()) {
1318 if(NETDATA.options.debug.visibility || this.debug) this.log('I am not visible');
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;
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');
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.');
1343 if(this.debug) this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
1349 if(this.debug) this.log('canBeAutoRefreshed(): I have a selection in place.');
1354 if(this.debug) this.log('canBeAutoRefreshed(): I am paused.');
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.');
1368 chartState.prototype.autoRefresh = function(callback) {
1369 if(this.canBeAutoRefreshed()) {
1370 this.updateChart(callback);
1373 if(typeof callback !== 'undefined')
1378 chartState.prototype._defaultsFromDownloadedChart = function(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());
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);
1389 this._defaultsFromDownloadedChart(this.chart);
1390 if(typeof callback === 'function') callback();
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;
1398 url: this.chart_url,
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);
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 + '"');
1412 .always(function() {
1413 if(typeof callback === 'function') callback();
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";
1423 if(this.debug) this.log('sizing element');
1426 $(this.element).css('width', this.width);
1429 $(this.element).css('height', this.height);
1431 if(NETDATA.chartDefaults.min_width)
1432 $(this.element).css('min-width', NETDATA.chartDefaults.min_width);
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>'
1441 // reset the creation datetime
1442 // since we overwrote the whole element
1444 if(this.debug) this.log(msg);
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;
1453 // show a message indicating the chart is loading
1454 chartState.prototype.info = function(msg) {
1455 this.message('info', this.id + ': ' + msg);
1458 chartState.prototype.init = function() {
1459 if(this.debug) this.log('created');
1461 this.info("loading...");
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);
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');
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');
1480 this.library = NETDATA.chartLibraries[this.library_name];
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;
1487 // the default mode for all charts
1488 this.setMode('auto');
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;
1495 state = new chartState(element);
1497 $(element).data('netdata-state-object', state);
1502 // ----------------------------------------------------------------------------------------------------------------
1503 // Library functions
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;
1514 // script.onabort = onError;
1515 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
1516 if(typeof callback === "function")
1517 script.onload = callback;
1519 var s = document.getElementsByTagName('script')[0];
1520 s.parentNode.insertBefore(script, s);
1522 else if(typeof callback === "function")
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);
1532 if (typeof fileref !== 'undefined')
1533 document.getElementsByTagName("head")[0].appendChild(fileref);
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;
1543 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
1545 r: parseInt(result[1], 16),
1546 g: parseInt(result[2], 16),
1547 b: parseInt(result[3], 16)
1551 NETDATA.colorLuminance = function(hex, lum) {
1552 // validate hex string
1553 hex = String(hex).replace(/[^0-9a-f]/gi, '');
1555 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
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);
1570 NETDATA.guid = function() {
1572 return Math.floor((1 + Math.random()) * 0x10000)
1577 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
1580 NETDATA.zeropad = function(x) {
1581 if(x > -10 && x < 10) return '0' + x.toString();
1582 else return x.toString();
1585 // user function to signal us the DOM has been
1587 NETDATA.updatedDom = function() {
1588 NETDATA.options.updated_dom = true;
1591 // ----------------------------------------------------------------------------------------------------------------
1593 NETDATA.chartRefresher1 = function(index) {
1594 // if(NETDATA.options.debug.mail_loop) console.log('NETDATA.chartRefresher(<targets, ' + index + ')');
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);
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;
1610 setTimeout(function() {
1611 NETDATA.chartRefresher1(0);
1612 }, NETDATA.options.current.idle_between_loops);
1615 var state = NETDATA.chartState(target);
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...');
1620 state.autoRefresh(function() {
1621 NETDATA.chartRefresher1(++index);
1625 if(NETDATA.options.debug.main_loop) console.log('waiting for next refresh...');
1626 NETDATA.options.auto_refresher_fast_weight = 0;
1628 setTimeout(function() {
1629 state.autoRefresh(function() {
1630 NETDATA.chartRefresher1(++index);
1632 }, NETDATA.options.current.idle_between_charts);
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);
1645 if(NETDATA.options.sequencial.length === 0)
1646 NETDATA.chartRefresher();
1648 var state = NETDATA.options.sequencial.pop();
1649 if(state.library.initialized)
1650 NETDATA.chartRefresher();
1652 state.autoRefresh(NETDATA.chartRefresher_sequencial);
1656 NETDATA.chartRefresher = function() {
1657 if(!NETDATA.options.current.parallel_refresher) {
1658 NETDATA.chartRefresher1(0);
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();
1672 NETDATA.options.parallel = new Array();
1673 NETDATA.options.sequencial = new Array();
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);
1679 if(!state.library.initialized)
1680 NETDATA.options.sequencial.push(state);
1682 NETDATA.options.parallel.push(state);
1685 if(NETDATA.options.parallel.length > 0) {
1686 NETDATA.options.parallel_jobs = NETDATA.options.parallel.length;
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);
1699 setTimeout(NETDATA.chartRefresher_sequencial,
1700 NETDATA.options.current.idle_between_charts);
1704 NETDATA.getDomCharts = function(callback) {
1705 NETDATA.options.updated_dom = false;
1707 NETDATA.options.targets = $('div[data-netdata]').filter(':visible');
1709 if(NETDATA.options.debug.main_loop)
1710 console.log('DOM updated - there are ' + NETDATA.options.targets.length + ' charts on page.');
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);
1720 if(typeof callback === 'function') callback();
1723 // this is the main function - where everything starts
1724 NETDATA.start = function() {
1725 // this should be called only once
1727 NETDATA.options.page_is_visible = true;
1729 $(window).blur(function() {
1730 NETDATA.options.page_is_visible = false;
1731 if(NETDATA.options.debug.focus) console.log('Lost Focus!');
1734 $(window).focus(function() {
1735 NETDATA.options.page_is_visible = true;
1736 if(NETDATA.options.debug.focus) console.log('Focus restored!');
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!');
1744 NETDATA.getDomCharts(function() {
1745 NETDATA.chartRefresher(0);
1749 // ----------------------------------------------------------------------------------------------------------------
1752 NETDATA.peityInitialize = function(callback) {
1753 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
1755 url: NETDATA.peity_js,
1760 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
1763 NETDATA.error(100, NETDATA.peity_js);
1765 .always(function() {
1766 if(typeof callback === "function")
1771 NETDATA.chartLibraries.peity.enabled = false;
1772 if(typeof callback === "function")
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() });
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() });
1790 // ----------------------------------------------------------------------------------------------------------------
1793 NETDATA.sparklineInitialize = function(callback) {
1794 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
1796 url: NETDATA.sparkline_js,
1801 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
1804 NETDATA.error(100, NETDATA.sparkline_js);
1806 .always(function() {
1807 if(typeof callback === "function")
1812 NETDATA.chartLibraries.sparkline.enabled = false;
1813 if(typeof callback === "function")
1818 NETDATA.sparklineChartUpdate = function(state, data) {
1819 state.sparkline_options.width = state.chartWidth();
1820 state.sparkline_options.height = state.chartHeight();
1822 $(state.element_chart).sparkline(data.result, state.sparkline_options);
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;
1874 state.sparkline_options = {
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,
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()
1925 $(state.element_chart).sparkline(data.result, state.sparkline_options);
1928 // ----------------------------------------------------------------------------------------------------------------
1935 NETDATA.dygraphSetSelection = function(state, t) {
1936 if(typeof state.dygraph_instance !== 'undefined') {
1937 var r = state.calculateRowForTime(t);
1939 state.dygraph_instance.setSelection(r);
1943 state.dygraph_instance.clearSelection();
1949 NETDATA.dygraphClearSelection = function(state, t) {
1950 if(typeof state.dygraph_instance !== 'undefined') {
1951 state.dygraph_instance.clearSelection();
1956 NETDATA.dygraphSmoothInitialize = function(callback) {
1958 url: NETDATA.dygraph_smooth_js,
1963 NETDATA.dygraph.smooth = true;
1964 smoothPlotter.smoothing = 0.3;
1966 .always(function() {
1967 if(typeof callback === "function")
1972 NETDATA.dygraphInitialize = function(callback) {
1973 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
1975 url: NETDATA.dygraph_js,
1980 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
1981 NETDATA.dygraphSmoothInitialize(callback);
1984 NETDATA.error(100, NETDATA.dygraph_js);
1985 if(typeof callback === "function")
1990 NETDATA.chartLibraries.dygraph.enabled = false;
1991 if(typeof callback === "function")
1996 NETDATA.dygraphChartUpdate = function(state, data) {
1997 var dygraph = state.dygraph_instance;
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
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,
2019 NETDATA.dygraphChartCreate = function(state, data) {
2020 if(NETDATA.options.debug.dygraph || state.debug) state.log('dygraphChartCreate()');
2022 var self = $(state.element);
2024 state.dygraph_options = {
2025 colors: self.data('dygraph-colors') || NETDATA.colors,
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,
2032 title: self.data('dygraph-title') || state.chart.title,
2033 titleHeight: self.data('dygraph-titleheight') || 19,
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,
2044 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
2045 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
2047 ylabel: state.chart.units,
2048 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
2050 // the function to plot the chart
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,
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,
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,
2064 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
2065 pointSize: self.data('dygraph-pointsize') || 1,
2067 // enabling this makes the chart with little square lines
2068 stepPlot: self.data('dygraph-stepplot') || false,
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,
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',
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,
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',
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); },
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,
2100 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
2104 ticker: Dygraph.dateTicker,
2105 axisLabelFormatter: function (d, gran) {
2106 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
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());
2116 valueFormatter: function (x) {
2117 // return (Math.round(x*100) / 100).toLocaleString();
2118 return state.legendFormatValue(x);
2122 legendFormatter: function(data) {
2123 var g = data.dygraph;
2125 var elements = state.element_legend_childs;
2127 // if the hidden div is not there
2128 // state is not managing the legend
2129 if(elements.hidden === null) return;
2131 if (typeof data.x === 'undefined') {
2132 state.legendReset();
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;
2146 drawCallback: function(dygraph, is_initial) {
2147 if(state.current.name !== 'auto') {
2148 if(NETDATA.options.debug.dygraph) state.log('dygraphDrawCallback()');
2150 var x_range = dygraph.xAxisRange();
2151 var after = Math.round(x_range[0]);
2152 var before = Math.round(x_range[1]);
2154 state.updateChartPanOrZoom(after, before);
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);
2163 highlightCallback: function(event, x, points, row, seriesName) {
2164 if(NETDATA.options.debug.dygraph || state.debug) state.log('dygraphHighlightCallback()');
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
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);
2174 state.globalSelectionSync(t);
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;
2180 unhighlightCallback: function(event) {
2181 if(NETDATA.options.debug.dygraph || state.debug) state.log('dygraphUnhighlightCallback()');
2182 state.unpauseChart();
2183 state.globalSelectionSyncStop();
2185 interactionModel : {
2186 mousedown: function(event, dygraph, context) {
2187 if(NETDATA.options.debug.dygraph || state.debug) state.log('interactionModel.mousedown()');
2188 state.globalSelectionSyncStop();
2190 if(NETDATA.options.debug.dygraph) state.log('dygraphMouseDown()');
2192 // Right-click should not initiate a zoom.
2193 if(event.button && event.button === 2) return;
2195 context.initializeMouseDown(event, dygraph, context);
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);
2204 state.setMode('zoom');
2205 state.globalSelectionSyncDelay();
2206 Dygraph.startZoom(event, dygraph, context);
2210 if (event.altKey || event.shiftKey) {
2211 state.setMode('zoom');
2212 state.globalSelectionSyncDelay();
2213 Dygraph.startZoom(event, dygraph, context);
2216 state.setMode('pan');
2217 state.globalSelectionSyncDelay();
2218 Dygraph.startPan(event, dygraph, context);
2222 mousemove: function(event, dygraph, context) {
2223 if(NETDATA.options.debug.dygraph || state.debug) state.log('interactionModel.mousemove()');
2225 if(context.isPanning) {
2226 state.globalSelectionSyncStop();
2227 state.globalSelectionSyncDelay();
2228 state.setMode('pan');
2229 Dygraph.movePan(event, dygraph, context);
2231 else if(context.isZooming) {
2232 state.globalSelectionSyncStop();
2233 state.globalSelectionSyncDelay();
2234 state.setMode('zoom');
2235 Dygraph.moveZoom(event, dygraph, context);
2238 mouseup: function(event, dygraph, context) {
2239 if(NETDATA.options.debug.dygraph || state.debug) state.log('interactionModel.mouseup()');
2241 if (context.isPanning) {
2242 state.globalSelectionSyncDelay();
2243 Dygraph.endPan(event, dygraph, context);
2245 else if (context.isZooming) {
2246 state.globalSelectionSyncDelay();
2247 Dygraph.endZoom(event, dygraph, context);
2250 click: function(event, dygraph, context) {
2251 if(NETDATA.options.debug.dygraph || state.debug) state.log('interactionModel.click()');
2252 /*Dygraph.cancelEvent(event);*/
2254 dblclick: function(event, dygraph, context) {
2255 if(NETDATA.options.debug.dygraph || state.debug) state.log('interactionModel.dblclick()');
2256 state.globalSelectionSyncStop();
2259 mousewheel: function(event, dygraph, context) {
2260 if(NETDATA.options.debug.dygraph || state.debug) state.log('interactionModel.mousewheel()');
2262 if(event.altKey || event.shiftKey) {
2263 state.globalSelectionSyncStop();
2264 state.globalSelectionSyncDelay();
2266 // http://dygraphs.com/gallery/interaction-api.js
2267 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
2268 var percentage = normal / 25;
2270 var before_old = state.current.before_ms;
2271 var after_old = state.current.after_ms;
2272 var range_old = before_old - after_old;
2274 var range = range_old * ( 1 - percentage );
2275 var dt = Math.round((range_old - range) / 2);
2277 var before = before_old - dt;
2278 var after = after_old + dt;
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());
2282 state.setMode('zoom');
2283 state.updateChartPanOrZoom(after, before);
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');
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);
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);
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;
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;
2330 state.dygraph_instance = new Dygraph(state.element_chart,
2331 data.result.data, state.dygraph_options);
2334 // ----------------------------------------------------------------------------------------------------------------
2337 NETDATA.morrisInitialize = function(callback) {
2338 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
2340 // morris requires raphael
2341 if(!NETDATA.chartLibraries.raphael.initialized) {
2342 if(NETDATA.chartLibraries.raphael.enabled) {
2343 NETDATA.raphaelInitialize(function() {
2344 NETDATA.morrisInitialize(callback);
2348 NETDATA.chartLibraries.morris.enabled = false;
2349 if(typeof callback === "function")
2354 NETDATA._loadCSS(NETDATA.morris_css);
2357 url: NETDATA.morris_js,
2362 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
2365 NETDATA.error(100, NETDATA.morris_js);
2367 .always(function() {
2368 if(typeof callback === "function")
2374 NETDATA.chartLibraries.morris.enabled = false;
2375 if(typeof callback === "function")
2380 NETDATA.morrisChartUpdate = function(state, data) {
2381 state.morris_instance.setData(data.result.data);
2384 NETDATA.morrisChartCreate = function(state, data) {
2386 state.morris_options = {
2387 element: state.element_chart_id,
2388 data: data.result.data,
2390 ykeys: data.dimension_names,
2391 labels: data.dimension_names,
2397 continuousLine: false,
2398 behaveLikeLine: false
2401 if(state.chart.chart_type === 'line')
2402 state.morris_instance = new Morris.Line(state.morris_options);
2404 else if(state.chart.chart_type === 'area') {
2405 state.morris_options.behaveLikeLine = true;
2406 state.morris_instance = new Morris.Area(state.morris_options);
2409 state.morris_instance = new Morris.Area(state.morris_options);
2412 // ----------------------------------------------------------------------------------------------------------------
2415 NETDATA.raphaelInitialize = function(callback) {
2416 if(typeof netdataStopRaphael === 'undefined') {
2418 url: NETDATA.raphael_js,
2423 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
2426 NETDATA.error(100, NETDATA.raphael_js);
2428 .always(function() {
2429 if(typeof callback === "function")
2434 NETDATA.chartLibraries.raphael.enabled = false;
2435 if(typeof callback === "function")
2440 NETDATA.raphaelChartUpdate = function(state, data) {
2441 $(state.element_chart).raphael(data.result, {
2442 width: state.chartWidth(),
2443 height: state.chartHeight()
2447 NETDATA.raphaelChartCreate = function(state, data) {
2448 $(state.element_chart).raphael(data.result, {
2449 width: state.chartWidth(),
2450 height: state.chartHeight()
2454 // ----------------------------------------------------------------------------------------------------------------
2457 NETDATA.googleInitialize = function(callback) {
2458 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
2460 url: NETDATA.google_js,
2465 NETDATA.registerChartLibrary('google', NETDATA.google_js);
2467 google.load('visualization', '1.1', {
2468 'packages': ['corechart', 'controls'],
2469 'callback': callback
2473 NETDATA.error(100, NETDATA.google_js);
2474 if(typeof callback === "function")
2479 NETDATA.chartLibraries.google.enabled = false;
2480 if(typeof callback === "function")
2485 NETDATA.googleChartUpdate = function(state, data) {
2486 var datatable = new google.visualization.DataTable(data.result);
2487 state.google_instance.draw(datatable, state.google_options);
2490 NETDATA.googleChartCreate = function(state, data) {
2491 var datatable = new google.visualization.DataTable(data.result);
2493 state.google_options = {
2494 // do not set width, height - the chart resizes itself
2495 //width: state.chartWidth(),
2496 //height: state.chartHeight(),
2498 title: state.chart.title,
2501 // title: "Time of Day",
2502 // format:'HH:mm:ss',
2503 viewWindowMode: 'maximized',
2514 title: state.chart.units,
2515 viewWindowMode: 'pretty',
2530 focusTarget: 'category',
2537 titlePosition: 'out',
2548 curveType: 'function',
2553 switch(state.chart.chart_type) {
2555 state.google_options.vAxis.viewWindowMode = 'maximized';
2556 state.google_instance = new google.visualization.AreaChart(state.element_chart);
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);
2570 state.google_options.lineWidth = 2;
2571 state.google_instance = new google.visualization.LineChart(state.element_chart);
2575 state.google_instance.draw(datatable, state.google_options);
2578 // ----------------------------------------------------------------------------------------------------------------
2581 NETDATA.easypiechartInitialize = function(callback) {
2582 if(typeof netdataStopEasypiechart === 'undefined') {
2584 url: NETDATA.easypiechart_js,
2589 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
2592 NETDATA.error(100, NETDATA.easypiechart_js);
2594 .always(function() {
2595 if(typeof callback === "function")
2600 NETDATA.chartLibraries.easypiechart.enabled = false;
2601 if(typeof callback === "function")
2606 NETDATA.easypiechartChartUpdate = function(state, data) {
2608 state.easypiechart_instance.update();
2611 NETDATA.easypiechartChartCreate = function(state, data) {
2612 var self = $(state.element);
2617 $(state.element_chart).data('data-percent', pcent);
2618 data.element_chart.innerHTML = value.toString();
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
2635 // ----------------------------------------------------------------------------------------------------------------
2636 // Charts Libraries Registration
2638 NETDATA.chartLibraries = {
2640 initialize: NETDATA.dygraphInitialize,
2641 create: NETDATA.dygraphChartCreate,
2642 update: NETDATA.dygraphChartUpdate,
2643 setSelection: NETDATA.dygraphSetSelection,
2644 clearSelection: NETDATA.dygraphClearSelection,
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';
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))
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;
2670 state.dygraph_sparkline = false;
2672 return state.dygraph_sparkline;
2676 initialize: NETDATA.sparklineInitialize,
2677 create: NETDATA.sparklineChartCreate,
2678 update: NETDATA.sparklineChartUpdate,
2680 clearSelection: null,
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; }
2691 initialize: NETDATA.peityInitialize,
2692 create: NETDATA.peityChartCreate,
2693 update: NETDATA.peityChartUpdate,
2695 clearSelection: null,
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; }
2706 initialize: NETDATA.morrisInitialize,
2707 create: NETDATA.morrisChartCreate,
2708 update: NETDATA.morrisChartUpdate,
2710 clearSelection: null,
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; }
2721 initialize: NETDATA.googleInitialize,
2722 create: NETDATA.googleChartCreate,
2723 update: NETDATA.googleChartUpdate,
2725 clearSelection: null,
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; }
2736 initialize: NETDATA.raphaelInitialize,
2737 create: NETDATA.raphaelChartCreate,
2738 update: NETDATA.raphaelChartUpdate,
2740 clearSelection: null,
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; }
2751 initialize: NETDATA.easypiechartInitialize,
2752 create: NETDATA.easypiechartChartCreate,
2753 update: NETDATA.easypiechartChartUpdate,
2755 clearSelection: null,
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; }
2767 NETDATA.registerChartLibrary = function(library, url) {
2768 if(NETDATA.options.debug.libraries)
2769 console.log("registering chart library: " + library);
2771 NETDATA.chartLibraries[library].url = url;
2772 NETDATA.chartLibraries[library].initialized = true;
2773 NETDATA.chartLibraries[library].enabled = true;
2776 // ----------------------------------------------------------------------------------------------------------------
2779 NETDATA.requiredJs = [
2780 NETDATA.serverDefault + 'lib/visible.js',
2781 NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js'
2784 NETDATA.loadRequiredJs = function(index, callback) {
2785 if(index >= NETDATA.requiredJs.length) {
2786 if(typeof callback === 'function')
2792 url: NETDATA.requiredJs[index],
2796 .success(function() {
2797 NETDATA.loadRequiredJs(++index, callback);
2800 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index]);
2804 NETDATA.errorReset();
2805 NETDATA._loadjQuery(function() {
2806 NETDATA.loadRequiredJs(0, function() {
2807 NETDATA._loadCSS(NETDATA.dashboard_css);
2808 if(typeof netdataDontStart === 'undefined' || !netdataDontStart)