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
15 //(function(window, document, undefined) {
16 // fix IE bug with console
17 if(!window.console){ window.console = {log: function(){} }; }
20 var NETDATA = window.NETDATA || {};
22 // ----------------------------------------------------------------------------------------------------------------
23 // Detect the netdata server
25 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
26 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
27 NETDATA._scriptSource = function(scripts) {
28 var script = null, base = null;
30 if(typeof document.currentScript !== 'undefined') {
31 script = document.currentScript;
34 var all_scripts = document.getElementsByTagName('script');
35 script = all_scripts[all_scripts.length - 1];
38 if (typeof script.getAttribute.length !== 'undefined')
41 script = script.getAttribute('src', -1);
43 var link = document.createElement('a');
44 link.setAttribute('href', script);
46 if(!link.protocol || !link.hostname) return null;
49 if(base) base += "//";
50 base += link.hostname;
52 if(link.port) base += ":" + link.port;
58 if(typeof netdataServer !== 'undefined')
59 NETDATA.serverDefault = netdataServer;
61 NETDATA.serverDefault = NETDATA._scriptSource();
63 if(NETDATA.serverDefault === null)
64 NETDATA.serverDefault = '';
65 else if(NETDATA.serverDefault.slice(-1) !== '/')
66 NETDATA.serverDefault += '/';
68 // default URLs for all the external files we need
69 // make them RELATIVE so that the whole thing can also be
70 // installed under a web server
71 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-1.11.3.min.js';
72 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity.min.js';
73 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline.min.js';
74 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart.min.js';
75 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined.js';
76 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter.js';
77 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-min.js';
78 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris.min.js';
79 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris.css';
80 NETDATA.dashboard_css = NETDATA.serverDefault + 'dashboard.css';
81 NETDATA.google_js = 'https://www.google.com/jsapi';
83 // these are the colors Google Charts are using
84 // we have them here to attempt emulate their look and feel on the other chart libraries
85 // http://there4.io/2012/05/02/google-chart-color-list/
86 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
87 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
88 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
90 NETDATA.colors = [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477', '#3B3EAC',
91 '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6', '#994499', '#22AA99',
92 '#6633CC', '#E67300', '#316395', '#8B0707', '#329262', '#3B3EAC' ];
94 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
95 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
96 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
98 // ----------------------------------------------------------------------------------------------------------------
99 // the defaults for all charts
101 // if the user does not specify any of these, the following will be used
103 NETDATA.chartDefaults = {
104 host: NETDATA.serverDefault, // the server to get data from
105 width: '100%', // the chart width - can be null
106 height: '100%', // the chart height - can be null
107 min_width: null, // the chart minimum width - can be null
108 library: 'dygraph', // the graphing library to use
109 method: 'average', // the grouping method
110 before: 0, // panning
111 after: -600, // panning
112 pixels_per_point: 1, // the detail of the chart
113 fill_luminance: 0.8 // luminance of colors in solit areas
116 // ----------------------------------------------------------------------------------------------------------------
120 readyCallback: null, // a callback when we load the required stuf
121 pauseCallback: null, // a callback when we are really paused
123 pause: false, // when enabled we don't auto-refresh the charts
125 targets: null, // an array of all the DOM elements that are
126 // currently visible (independently of their
127 // viewport visibility)
129 updated_dom: true, // when true, the DOM has been updated with
130 // new elements we have to check.
132 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
133 // rendering charts continiously.
134 // used with .current.fast_render_timeframe
136 page_is_visible: true, // when true, this page is visible
138 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
139 // auto-refresher for some time (when a chart is
140 // performing pan or zoom, we need to stop refreshing
141 // all other charts, to have the maximum speed for
142 // rendering the chart that is panned or zoomed).
143 // Used with .current.global_pan_sync_time
145 last_resized: 0, // the timestamp of the last resize request
147 crossDomainAjax: false, // enable this to request crossDomain AJAX
149 // the current profile
150 // we may have many...
152 pixels_per_point: 1, // the minimum pixels per point for all charts
153 // increase this to speed javascript up
154 // each chart library has its own limit too
155 // the max of this and the chart library is used
156 // the final is calculated every time, so a change
157 // here will have immediate effect on the next chart
160 idle_between_charts: 100, // ms - how much time to wait between chart updates
162 fast_render_timeframe: 100, // ms - render continously until this time of continious
163 // rendering has been reached
164 // this setting is used to make it render e.g. 10
165 // charts at once, sleep idle_between_charts time
166 // and continue for another 10 charts.
168 idle_between_loops: 200, // ms - if all charts have been updated, wait this
169 // time before starting again.
171 idle_lost_focus: 500, // ms - when the window does not have focus, check
172 // if focus has been regained, every this time
174 global_pan_sync_time: 1500, // ms - when you pan or zoon a chart, the background
175 // autorefreshing of charts is paused for this amount
178 sync_selection_delay: 2500, // ms - when you pan or zoom a chart, wait this amount
179 // of time before setting up synchronized selections
182 sync_selection: true, // enable or disable selection sync
184 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
186 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
188 update_only_visible: true, // enable or disable visibility management
190 parallel_refresher: true, // enable parallel refresh of charts
192 color_fill_opacity: {
213 if(NETDATA.options.debug.main_loop) console.log('welcome to NETDATA');
215 window.onresize = function(event) {
216 NETDATA.options.last_resized = new Date().getTime();
219 window.onscroll = function(event) {
220 // FIXME targets should be states not DOM elements
221 for(i = 0; i < NETDATA.options.targets.length ;i++) {
222 var state = NETDATA.chartState(NETDATA.options.targets[i])
227 // ----------------------------------------------------------------------------------------------------------------
230 NETDATA.errorCodes = {
231 100: { message: "Cannot load chart library", alert: true },
232 101: { message: "Cannot load jQuery", alert: true },
233 402: { message: "Chart library not found", alert: false },
234 403: { message: "Chart library not enabled/is failed", alert: false },
235 404: { message: "Chart not found", alert: false }
237 NETDATA.errorLast = {
243 NETDATA.error = function(code, msg) {
244 NETDATA.errorLast.code = code;
245 NETDATA.errorLast.message = msg;
246 NETDATA.errorLast.datetime = new Date().getTime();
248 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
250 if(NETDATA.errorCodes[code].alert)
251 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
254 NETDATA.errorReset = function() {
255 NETDATA.errorLast.code = 0;
256 NETDATA.errorLast.message = "You are doing fine!";
257 NETDATA.errorLast.datetime = 0;
260 // ----------------------------------------------------------------------------------------------------------------
263 // When multiple charts need the same chart, we avoid downloading it
264 // multiple times (and having it in browser memory multiple time)
265 // by using this registry.
267 // Every time we download a chart definition, we save it here with .add()
268 // Then we try to get it back with .get(). If that fails, we download it.
270 NETDATA.chartRegistry = {
273 fixid: function(id) {
274 return id.replace(/:/g, "_").replace(/\//g, "_");
277 add: function(host, id, data) {
278 host = this.fixid(host);
281 if(typeof this.charts[host] === 'undefined')
282 this.charts[host] = {};
284 //console.log('added ' + host + '/' + id);
285 this.charts[host][id] = data;
288 get: function(host, id) {
289 host = this.fixid(host);
292 if(typeof this.charts[host] === 'undefined')
295 if(typeof this.charts[host][id] === 'undefined')
298 //console.log('cached ' + host + '/' + id);
299 return this.charts[host][id];
302 downloadAll: function(host, callback) {
303 while(host.slice(-1) === '/')
304 host = host.substring(0, host.length - 1);
309 url: host + '/api/v1/charts',
310 crossDomain: NETDATA.options.crossDomainAjax,
314 .done(function(data) {
315 var h = NETDATA.chartRegistry.fixid(host);
316 //console.log('downloaded all charts from ' + host + ' (' + h + ')');
317 self.charts[h] = data.charts;
318 if(typeof callback === 'function')
322 if(typeof callback === 'function')
328 // ----------------------------------------------------------------------------------------------------------------
329 // Global Pan and Zoom on charts
331 // Using this structure are synchronize all the charts, so that
332 // when you pan or zoom one, all others are automatically refreshed
333 // to the same timespan.
335 NETDATA.globalPanAndZoom = {
336 seq: 0, // timestamp ms
337 // every time a chart is panned or zoomed
338 // we set the timestamp here
339 // then we use it as a sequence number
340 // to find if other charts are syncronized
343 master: null, // the master chart (state), to which all others
346 force_before_ms: null, // the timespan to sync all other charts
347 force_after_ms: null,
350 setMaster: function(state, after, before) {
351 if(!NETDATA.options.current.sync_pan_and_zoom) return;
353 if(this.master !== null && this.master !== state)
354 this.master.resetChart();
356 var now = new Date().getTime();
359 this.force_after_ms = after;
360 this.force_before_ms = before;
361 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
365 clearMaster: function() {
366 if(!NETDATA.options.current.sync_pan_and_zoom) return;
368 if(this.master !== null) {
369 var state = this.master;
370 this.master = null; // prevent infinite recursion
373 NETDATA.options.auto_refresher_stop_until = 0;
378 this.force_after_ms = null;
379 this.force_before_ms = null;
382 // is the given state the master of the global
383 // pan and zoom sync?
384 isMaster: function(state) {
385 if(this.master === state) return true;
389 // are we currently have a global pan and zoom sync?
390 isActive: function() {
391 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
395 // check if a chart, other than the master
396 // needs to be refreshed, due to the global pan and zoom
397 shouldBeAutoRefreshed: function(state) {
398 if(this.master === null || this.seq === 0)
401 if(state.needsResize())
404 if(state.follows_global === this.seq)
411 // ----------------------------------------------------------------------------------------------------------------
412 // Our state object, where all per-chart values are stored
414 chartState = function(element) {
418 uuid: NETDATA.guid(), // GUID - a unique identifier for the chart
419 id: self.data('netdata'), // string - the name of chart
421 // the user given dimensions of the element
422 width: self.data('width') || NETDATA.chartDefaults.width,
423 height: self.data('height') || NETDATA.chartDefaults.height,
425 // string - the netdata server URL, without any path
426 host: self.data('host') || NETDATA.chartDefaults.host,
428 // string - the grouping method requested by the user
429 method: self.data('method') || NETDATA.chartDefaults.method,
431 // the time-range requested by the user
432 after: self.data('after') || NETDATA.chartDefaults.after,
433 before: self.data('before') || NETDATA.chartDefaults.before,
435 // the pixels per point requested by the user
436 pixels_per_point: self.data('pixels-per-point') || 1,
437 points: self.data('points') || null,
439 // the dimensions requested by the user
440 dimensions: self.data('dimensions') || null,
442 // the chart library requested by the user
443 library_name: self.data('chart-library') || NETDATA.chartDefaults.library,
444 library: null, // object - the chart library used
446 element: element, // the element already created by the user
447 element_message: null,
448 element_loading: null,
449 element_chart: null, // the element with the chart
450 element_chart_id: null,
451 element_legend: null, // the element with the legend of the chart (if created by us)
452 element_legend_id: null,
453 element_legend_childs: {
461 chart_url: null, // string - the url to download chart info
462 chart: null, // object - the chart as downloaded from the server
464 downloaded_ms: 0, // milliseconds - the timestamp we downloaded the chart
465 created_ms: 0, // boolean - the timestamp the chart was created
466 validated: false, // boolean - has the chart been validated?
467 enabled: true, // boolean - is the chart enabled for refresh?
468 paused: false, // boolean - is the chart paused for any reason?
469 selected: false, // boolean - is the chart shown a selection?
470 debug: false, // boolean - console.log() debug info about this chart
472 updates_counter: 0, // numeric - the number of refreshes made so far
473 updates_since_last_creation: 0,
475 follows_global: 0, // the sequence number of the global synchronization
477 // Used with NETDATA.globalPanAndZoom.seq
479 last_resized: 0, // the last time the chart was resized
481 mode: null, // auto, pan, zoom
482 // this is a pointer to one of the sub-classes below
487 url: 'invalid://', // string - the last url used to update the chart
488 last_autorefreshed: 0, // milliseconds - the timestamp of last automatic refresh
489 view_update_every: 0, // milliseconds - the minimum acceptable refresh duration
490 after_ms: 0, // milliseconds - the first timestamp of the data
491 before_ms: 0, // milliseconds - the last timestamp of the data
492 points: 0, // number - the number of points in the data
493 data: null, // the last downloaded data
494 force_update_at: 0, // the timestamp to force the update at
495 force_before_ms: null,
496 force_after_ms: null,
497 requested_before_ms: null,
498 requested_after_ms: null,
499 first_entry_ms: null,
505 url: 'invalid://', // string - the last url used to update the chart
506 last_autorefreshed: 0, // milliseconds - the timestamp of last refresh
507 view_update_every: 0, // milliseconds - the minimum acceptable refresh duration
508 after_ms: 0, // milliseconds - the first timestamp of the data
509 before_ms: 0, // milliseconds - the last timestamp of the data
510 points: 0, // number - the number of points in the data
511 data: null, // the last downloaded data
512 force_update_at: 0, // the timestamp to force the update at
513 force_before_ms: null,
514 force_after_ms: null,
515 requested_before_ms: null,
516 requested_after_ms: null,
517 first_entry_ms: null,
523 url: 'invalid://', // string - the last url used to update the chart
524 last_autorefreshed: 0, // milliseconds - the timestamp of last refresh
525 view_update_every: 0, // milliseconds - the minimum acceptable refresh duration
526 after_ms: 0, // milliseconds - the first timestamp of the data
527 before_ms: 0, // milliseconds - the last timestamp of the data
528 points: 0, // number - the number of points in the data
529 data: null, // the last downloaded data
530 force_update_at: 0, // the timestamp to force the update at
531 force_before_ms: null,
532 force_after_ms: null,
533 requested_before_ms: null,
534 requested_after_ms: null,
535 first_entry_ms: null,
539 refresh_dt_ms: 0, // milliseconds - the time the last refresh took
540 refresh_dt_element_name: self.data('dt-element-name') || null, // string - the element to print refresh_dt_ms
541 refresh_dt_element: null
547 // ----------------------------------------------------------------------------------------------------------------
548 // global selection sync
550 NETDATA.globalSelectionSync = {
556 // prevent to global selection sync for some time
557 chartState.prototype.globalSelectionSyncDelay = function() {
558 if(!NETDATA.options.current.sync_selection) return;
559 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
562 // can we globally apply selection sync?
563 chartState.prototype.globalSelectionSyncAbility = function() {
564 if(!NETDATA.options.current.sync_selection) return false;
565 if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime()) return false;
569 // this chart is the master of the global selection sync
570 chartState.prototype.globalSelectionSyncBeMaster = function() {
572 if(NETDATA.globalSelectionSync.state === this) {
573 if(this.debug) this.log('sync: I am the master already.');
577 if(NETDATA.globalSelectionSync.state) {
578 if(this.debug) this.log('sync: I am not the sync master. Resetting global sync.');
579 this.globalSelectionSyncStop();
583 if(this.debug) this.log('sync: becoming sync master.');
584 this.selected = true;
585 NETDATA.globalSelectionSync.state = this;
587 // find the all slaves
589 // FIXME targets should be states not DOM elements
590 $.each(NETDATA.options.targets, function(i, target) {
591 var st = NETDATA.chartState(target);
593 if(self.debug) st.log('sync: not adding me to sync');
595 else if(st.globalSelectionSyncIsEligible()) {
596 if(self.debug) st.log('sync: adding to sync as slave');
597 st.globalSelectionSyncBeSlave();
602 // can the chart participate to the global selection sync as a slave?
603 chartState.prototype.globalSelectionSyncIsEligible = function() {
604 if(this.library !== null && typeof this.library.setSelection === 'function' && this.isVisible() && this.created_ms !== 0)
609 // this chart is a slave of the global selection sync
610 chartState.prototype.globalSelectionSyncBeSlave = function() {
611 if(NETDATA.globalSelectionSync.state !== this)
612 NETDATA.globalSelectionSync.slaves.push(this);
615 // sync all the visible charts to the given time
616 // this is to be called from the chart libraries
617 chartState.prototype.globalSelectionSync = function(t) {
618 if(!this.globalSelectionSyncAbility()) {
619 if(this.debug) this.log('sync: cannot sync (yet?).');
623 if(this.debug) this.log('sync: trying to be sync master.');
624 this.globalSelectionSyncBeMaster();
627 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
629 // since we are the sync master, we should not call state.setSelection()
630 // the chart library is taking care of visualizing our selection.
631 if(self.debug) st.log('sync: ignoring me from set selection');
634 if(self.debug) st.log('sync: showing master selection');
640 // stop syncing all charts to the given time
641 chartState.prototype.globalSelectionSyncStop = function() {
642 if(NETDATA.globalSelectionSync.slaves.length) {
643 if(this.debug) this.log('sync: cleaning up...');
645 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
647 if(self.debug) st.log('sync: not adding me to sync stop');
650 if(self.debug) st.log('sync: removed slave from sync');
655 NETDATA.globalSelectionSync.slaves = [];
656 NETDATA.globalSelectionSync.state = null;
659 // since we are the sync master, we should not call this.clearSelection()
660 // dygraphs is taking care of visualizing our selection.
661 this.selected = false;
664 chartState.prototype.setSelection = function(t) {
665 if(typeof this.library.setSelection === 'function') {
666 if(this.library.setSelection(this, t))
667 this.selected = true;
669 this.selected = false;
671 else this.selected = true;
673 if(this.selected && this.debug) this.log('selection set to ' + t.toString());
675 return this.selected;
678 chartState.prototype.clearSelection = function() {
680 if(typeof this.library.clearSelection === 'function') {
681 if(this.library.clearSelection(this))
682 this.selected = false;
684 this.selected = true;
686 else this.selected = false;
688 if(!this.selected && this.debug) this.log('selection cleared');
692 return this.selected;
695 // find if a timestamp (ms) is shown in the current chart
696 chartState.prototype.timeIsVisible = function(t) {
697 if(t >= this.current.after_ms && t <= this.current.before_ms)
702 chartState.prototype.calculateRowForTime = function(t) {
703 if(!this.timeIsVisible(t)) return -1;
704 return Math.floor((t - this.current.after_ms) / this.current.view_update_every);
707 // ----------------------------------------------------------------------------------------------------------------
710 chartState.prototype.log = function(msg) {
711 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
714 chartState.prototype.pauseChart = function() {
716 if(this.debug) this.log('paused');
721 chartState.prototype.unpauseChart = function() {
723 if(this.debug) this.log('unpaused');
728 chartState.prototype.resetChart = function() {
729 if(NETDATA.globalPanAndZoom.isMaster(this) && this.isVisible())
730 NETDATA.globalPanAndZoom.clearMaster();
732 this.follows_global = 0;
734 this.clearSelection();
736 this.setMode('auto');
737 this.current.force_update_at = 0;
738 this.current.force_before_ms = null;
739 this.current.force_after_ms = null;
740 this.current.last_autorefreshed = 0;
742 this.selected = false;
746 // do not update the chart here
747 // or the chart will flip-flop when it is the master
748 // of a selection sync and another chart becomes
750 if(!NETDATA.options.current.sync_pan_and_zoom && this.isVisible())
754 chartState.prototype.setMode = function(m) {
756 if(this.current.name === m) return;
758 this[m].url = this.current.url;
759 this[m].last_autorefreshed = this.current.last_autorefreshed;
760 this[m].view_update_every = this.current.view_update_every;
761 this[m].after_ms = this.current.after_ms;
762 this[m].before_ms = this.current.before_ms;
763 this[m].points = this.current.points;
764 this[m].data = this.current.data;
765 this[m].requested_before_ms = this.current.requested_before_ms;
766 this[m].requested_after_ms = this.current.requested_after_ms;
767 this[m].first_entry_ms = this.current.first_entry_ms;
768 this[m].last_entry_ms = this.current.last_entry_ms;
772 this.current = this.auto;
774 this.current = this.pan;
775 else if(m === 'zoom')
776 this.current = this.zoom;
778 this.current = this.auto;
780 this.current.force_update_at = 0;
781 this.current.force_before_ms = null;
782 this.current.force_after_ms = null;
784 if(this.debug) this.log('mode set to ' + this.current.name);
787 chartState.prototype._minPanOrZoomStep = function() {
788 return (((this.current.before_ms - this.current.after_ms) / this.current.points) * ((this.current.points * 5 / 100) + 1) );
789 // return this.current.view_update_every * 10;
792 chartState.prototype._shouldBeMoved = function(old_after, old_before, new_after, new_before) {
793 var dt_after = Math.abs(old_after - new_after);
794 var dt_before = Math.abs(old_before - new_before);
795 var old_range = old_before - old_after;
797 var new_range = new_before - new_after;
798 var dt = Math.abs(old_range - new_range);
799 var step = Math.max(dt_after, dt_before, dt);
801 var min_step = this._minPanOrZoomStep();
802 if(new_range < old_range && new_range / this.chartWidth() < 100) {
803 if(this.debug) this.log('_shouldBeMoved(' + (new_after / 1000).toString() + ' - ' + (new_before / 1000).toString() + '): minimum point size: 0.10, wanted point size: ' + (new_range / this.chartWidth() / 1000).toString() + ': TOO SMALL RANGE');
807 if(step >= min_step) {
808 if(this.debug) this.log('_shouldBeMoved(' + (new_after / 1000).toString() + ' - ' + (new_before / 1000).toString() + '): minimum step: ' + (min_step / 1000).toString() + ', this step: ' + (step / 1000).toString() + ': YES');
812 if(this.debug) this.log('_shouldBeMoved(' + (new_after / 1000).toString() + ' - ' + (new_before / 1000).toString() + '): minimum step: ' + (min_step / 1000).toString() + ', this step: ' + (step / 1000).toString() + ': NO');
817 chartState.prototype.updateChartPanOrZoom = function(after, before) {
820 if(this.current.name === 'auto') {
821 if(this.debug) this.log('updateChartPanOrZoom(): caller did not set proper mode');
825 if(!this.current.force_after_ms || !this.current.force_before_ms) {
826 if(this.debug) this.log('updateChartPanOrZoom(' + (after / 1000).toString() + ' - ' + (before / 1000).toString() + '): INIT');
829 else if(this._shouldBeMoved(this.current.force_after_ms, this.current.force_before_ms, after, before) && this._shouldBeMoved(this.current.after_ms, this.current.before_ms, after, before)) {
830 if(this.debug) this.log('updateChartPanOrZoom(' + (after / 1000).toString() + ' - ' + (before / 1000).toString() + '): FORCE CHANGE from ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
833 else if(this._shouldBeMoved(this.current.requested_after_ms, this.current.requested_before_ms, after, before) && this._shouldBeMoved(this.current.after_ms, this.current.before_ms, after, before)) {
834 if(this.debug) this.log('updateChartPanOrZoom(' + (after / 1000).toString() + ' - ' + (before / 1000).toString() + '): REQUESTED CHANGE from ' + (this.current.requested_after_ms / 1000).toString() + ' - ' + (this.current.requested_before_ms / 1000).toString());
839 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
840 this.current.force_after_ms = after;
841 this.current.force_before_ms = before;
842 NETDATA.globalPanAndZoom.setMaster(this, after, before);
846 if(this.debug) this.log('updateChartPanOrZoom(' + (after / 1000).toString() + ' - ' + (before / 1000).toString() + '): IGNORE');
850 chartState.prototype.legendFormatValue = function(value) {
851 if(typeof value !== 'number' || value === null) return '';
853 var abs = Math.abs(value);
854 if(abs >= 1) return (Math.round(value * 100) / 100).toLocaleString();
855 if(abs >= 0.1) return (Math.round(value * 1000) / 1000).toLocaleString();
856 return (Math.round(value * 10000) / 10000).toLocaleString();
859 chartState.prototype.legendSetLabelValue = function(label, string) {
860 if(typeof this.element_legend_childs.series[label] === 'undefined')
863 if(this.element_legend_childs.series[label].value !== null)
864 this.element_legend_childs.series[label].value.innerHTML = string;
866 if(this.element_legend_childs.series[label].user !== null)
867 this.element_legend_childs.series[label].user.innerHTML = string;
870 chartState.prototype.legendSetDate = function(ms) {
871 if(typeof ms !== 'number') {
872 this.legendUndefined();
876 var d = new Date(ms);
878 if(this.element_legend_childs.title_date)
879 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
881 if(this.element_legend_childs.title_time)
882 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
884 if(this.element_legend_childs.title_units)
885 this.element_legend_childs.title_units.innerHTML = this.chart.units;
888 chartState.prototype.legendUndefined = function() {
889 if(this.element_legend_childs.title_date)
890 this.element_legend_childs.title_date.innerHTML = ' ';
892 if(this.element_legend_childs.title_time)
893 this.element_legend_childs.title_time.innerHTML = this.chart.name;
895 if(this.element_legend_childs.title_units)
896 this.element_legend_childs.title_units.innerHTML = ' ';
899 chartState.prototype.legendShowLatestValues = function() {
900 if(this.chart === null) return;
901 if(this.selected) return;
903 if(this.current.data === null || this.element_legend_childs.series === null) {
904 this.legendUndefined();
908 if(Math.abs(this.current.data.last_entry_t - this.current.data.before) <= this.current.data.view_update_every)
909 this.legendSetDate(this.current.data.before * 1000);
911 this.legendUndefined();
913 for(var i = 0; i < this.current.data.dimension_names.length; i++) {
914 if(typeof this.current.data.dimension_names[i] === 'undefined')
917 if(typeof this.element_legend_childs.series[this.current.data.dimension_names[i]] === 'undefined')
920 if(Math.abs(this.current.data.last_entry_t - this.current.data.before) <= this.current.data.view_update_every)
921 this.legendSetLabelValue(this.current.data.dimension_names[i], this.legendFormatValue(this.current.data.result_latest_values[i]));
923 this.legendSetLabelValue(this.current.data.dimension_names[i], '');
927 chartState.prototype.legendReset = function() {
928 this.legendShowLatestValues();
931 chartState.prototype.legendUpdateDOM = function() {
932 if(!this.hasLegend()) return;
936 // check that the legend DOM is up to date for the downloaded dimensions
937 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
938 // this.log('the legend does not have any series - requesting legend update');
941 else if(this.current.data === null) {
942 // this.log('the chart does not have any data - requesting legend update');
946 // this.log('checking existing legend');
947 for(var i = 0; i < this.current.data.dimension_names.length; i++) {
948 if(typeof this.element_legend_childs.series[this.current.data.dimension_names[i]] === 'undefined') {
949 // this.log('legend is incosistent - missing dimension:' + this.current.data.dimension_names[i]);
953 else if(Math.abs(this.current.data.last_entry_t - this.current.data.before) <= this.current.data.view_update_every) {
954 // this.log('setting legend of ' + this.current.data.dimension_names[i] + ' to ' + this.current.data.latest_values[i]);
955 this.legendSetLabelValue(this.current.data.dimension_names[i], this.legendFormatValue(this.current.data.latest_values[i]));
962 if(this.debug) this.log('updating Legend DOM');
964 this.element_legend.innerHTML = '';
966 this.element_legend_childs = {
967 title_date: document.createElement('span'),
968 title_time: document.createElement('span'),
969 title_units: document.createElement('span'),
973 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
974 this.element_legend.appendChild(this.element_legend_childs.title_date);
976 this.element_legend.appendChild(document.createElement('br'));
978 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
979 this.element_legend.appendChild(this.element_legend_childs.title_time);
981 this.element_legend.appendChild(document.createElement('br'));
983 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
984 this.element_legend.appendChild(this.element_legend_childs.title_units);
986 this.element_legend.appendChild(document.createElement('br'));
988 var nano = document.createElement('div');
989 nano.className = 'netdata-legend-series';
990 this.element_legend.appendChild(nano);
992 var content = document.createElement('div');
993 content.className = 'netdata-legend-series-content';
994 nano.appendChild(content);
997 var genLabel = function(state, parent, name, count) {
998 var c = count % NETDATA.colors.length;
1000 var user_element = null;
1001 var user_id = self.data('show-value-of-' + name + '-at') || null;
1002 if(user_id) user_element = document.getElementById(user_id);
1004 state.element_legend_childs.series[name] = {
1005 name: document.createElement('span'),
1006 value: document.createElement('span'),
1010 var label = state.element_legend_childs.series[name];
1012 label.name.className += ' netdata-legend-name';
1013 label.value.className += ' netdata-legend-value';
1014 label.name.title = name;
1015 label.value.title = name;
1017 var rgb = NETDATA.colorHex2Rgb(NETDATA.colors[c]);
1018 label.name.innerHTML = '<table class="netdata-legend-name-table-'
1019 + state.chart.chart_type
1020 + '" style="background-color: '
1021 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current.color_fill_opacity[state.chart.chart_type] + ')'
1022 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
1024 var text = document.createTextNode(' ' + name);
1025 label.name.appendChild(text);
1027 label.name.style.color = NETDATA.colors[c];
1028 label.value.style.color = NETDATA.colors[c];
1031 parent.appendChild(document.createElement('br'));
1033 parent.appendChild(label.name);
1034 parent.appendChild(label.value);
1037 if(this.current.data) {
1039 $.each(me.current.data.dimension_names, function(i, d) {
1040 genLabel(me, content, d, i);
1045 $.each(me.chart.dimensions, function(i, d) {
1046 genLabel(me, content, d.name, i);
1050 // create a hidden div to be used for hidding
1051 // the original legend of the chart library
1052 var el = document.createElement('div');
1053 this.element_legend.appendChild(el);
1054 el.style.display = 'none';
1056 this.element_legend_childs.hidden = document.createElement('div');
1057 el.appendChild(this.element_legend_childs.hidden);
1058 nano.appendChild(el);
1060 $(nano).nanoScroller({
1061 paneClass: 'netdata-legend-series-pane',
1062 sliderClass: 'netdata-legend-series-slider',
1063 contentClass: 'netdata-legend-series-content',
1064 enabledClass: '__enabled',
1065 flashedClass: '__flashed',
1066 activeClass: '__active',
1071 this.legendShowLatestValues();
1074 chartState.prototype.createChartDOM = function() {
1075 if(this.debug) this.log('creating DOM');
1077 if(this.hasLegend()) {
1078 this.element_chart_id = this.library_name + '-' + this.uuid + '-chart';
1079 this.element_chart = document.createElement('div');
1080 this.element_chart.className += ' netdata-chart-with-legend-right';
1081 this.element_chart.className += ' netdata-' + this.library_name + '-chart-with-legend-right';
1082 this.element_chart.id = this.element_chart_id;
1083 $(this.element_chart).data('netdata-state-object', this);
1084 this.element.appendChild(this.element_chart);
1086 this.element_legend_id = this.library_name + '-' + this.uuid + '-legend';
1087 this.element_legend = document.createElement('div');
1088 this.element_legend.className += ' netdata-chart-legend';
1089 this.element_legend.className += ' netdata-' + this.library_name + '-legend';
1090 this.element_legend.id = this.element_legend_id;
1091 $(this.element_legend).data('netdata-state-object', this);
1092 this.element.appendChild(this.element_legend);
1093 this.element_legend_childs.series = null;
1094 this.legendUpdateDOM();
1097 this.element_chart_id = this.library_name + '-' + this.uuid + '-chart';
1098 this.element_chart = document.createElement('div');
1099 this.element_chart.className += ' netdata-chart';
1100 this.element_chart.className += ' netdata-' + this.library_name + '-chart';
1101 this.element_chart.id = this.element_chart_id;
1102 $(this.element_chart).data('netdata-state-object', this);
1103 this.element.appendChild(this.element_chart);
1105 this.element_legend_id = null;
1106 this.element_legend = null;
1107 this.element_legend_childs.series = null;
1110 // in case the user has an active global selection sync in place
1112 this.globalSelectionSyncStop();
1115 chartState.prototype.hasLegend = function() {
1116 if(typeof this.___hasLegendCache___ !== 'undefined')
1117 return this.___hasLegendCache___;
1120 if(this.library && this.library.legend(this) === 'right-side') {
1121 var legend = $(this.element).data('legend') || 'yes';
1122 if(legend === 'yes') leg = true;
1125 this.___hasLegendCache___ = leg;
1129 chartState.prototype.legendWidth = function() {
1130 return (this.hasLegend())?110:0;
1133 chartState.prototype.legendHeight = function() {
1134 return $(this.element).height();
1137 chartState.prototype.chartWidth = function() {
1138 return $(this.element).width() - this.legendWidth();
1141 chartState.prototype.chartHeight = function() {
1142 return $(this.element).height();
1145 chartState.prototype.chartPixelsPerPoint = function() {
1146 // force an options provided detail
1147 var px = this.pixels_per_point;
1149 if(this.library && px < this.library.pixels_per_point(this))
1150 px = this.library.pixels_per_point(this);
1152 if(px < NETDATA.options.current.pixels_per_point)
1153 px = NETDATA.options.current.pixels_per_point;
1158 chartState.prototype.needsResize = function() {
1159 return (this.library && !this.library.autoresize() && this.last_resized < NETDATA.options.last_resized);
1162 chartState.prototype.resizeChart = function() {
1163 if(this.needsResize()) {
1164 if(this.debug) this.log('forcing re-generation due to window resize.');
1165 this.created_ms = 0;
1166 this.last_resized = new Date().getTime();
1170 chartState.prototype.chartURL = function() {
1173 if(NETDATA.globalPanAndZoom.isActive()) {
1174 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
1175 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
1176 this.follows_global = NETDATA.globalPanAndZoom.seq;
1179 before = this.current.force_before_ms !== null ? Math.round(this.current.force_before_ms / 1000) : this.before;
1180 after = this.current.force_after_ms !== null ? Math.round(this.current.force_after_ms / 1000) : this.after;
1181 this.follows_global = 0;
1184 this.current.requested_after_ms = after * 1000;
1185 this.current.requested_before_ms = before * 1000;
1187 this.current.points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
1189 // build the data URL
1190 this.current.url = this.chart.data_url;
1191 this.current.url += "&format=" + this.library.format();
1192 this.current.url += "&points=" + this.current.points.toString();
1193 this.current.url += "&group=" + this.method;
1194 this.current.url += "&options=" + this.library.options();
1195 this.current.url += '|jsonwrap';
1198 this.current.url += "&after=" + after.toString();
1201 this.current.url += "&before=" + before.toString();
1204 this.current.url += "&dimensions=" + this.dimensions;
1206 if(NETDATA.options.debug.chart_data_url || this.debug) this.log('chartURL(): ' + this.current.url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.current.points + ' library: ' + this.library_name);
1209 chartState.prototype.updateChartWithData = function(data) {
1210 if(this.debug) this.log('got data from netdata server');
1211 this.current.data = data;
1213 var started = new Date().getTime();
1215 // if the result is JSON, find the latest update-every
1216 if(typeof data === 'object') {
1217 if(typeof data.view_update_every !== 'undefined')
1218 this.current.view_update_every = data.view_update_every * 1000;
1220 if(typeof data.after !== 'undefined')
1221 this.current.after_ms = data.after * 1000;
1223 if(typeof data.before !== 'undefined')
1224 this.current.before_ms = data.before * 1000;
1226 if(typeof data.first_entry_t !== 'undefined')
1227 this.current.first_entry_ms = data.first_entry_t * 1000;
1229 if(typeof data.last_entry_t !== 'undefined')
1230 this.current.last_entry_ms = data.last_entry_t * 1000;
1232 if(typeof data.points !== 'undefined')
1233 this.current.points = data.points;
1238 this.updates_counter++;
1241 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
1243 if(this.current.force_after_ms)
1244 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
1246 this.log('STATUS: forced: unset');
1248 this.log('STATUS: requested: ' + (this.current.requested_after_ms / 1000).toString() + ' - ' + (this.current.requested_before_ms / 1000).toString());
1249 this.log('STATUS: rendered : ' + (this.current.after_ms / 1000).toString() + ' - ' + (this.current.before_ms / 1000).toString());
1250 this.log('STATUS: points : ' + (this.current.points).toString() + ', min step: ' + (this._minPanOrZoomStep() / 1000).toString());
1253 // this may force the chart to be re-created
1256 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
1257 if(this.debug) this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
1258 this.created_ms = 0;
1261 if(this.created_ms > 0 && typeof this.library.update === 'function') {
1262 if(this.debug) this.log('updating chart...');
1264 // check and update the legend
1265 this.legendUpdateDOM();
1267 this.updates_since_last_creation++;
1268 if(NETDATA.options.debug.chart_errors) {
1269 this.library.update(this, data);
1273 this.library.update(this, data);
1276 this.error('chart "' + this.id + '" failed to be updated as ' + this.library_name);
1281 if(this.debug) this.log('creating chart...');
1283 this.createChartDOM();
1284 this.updates_since_last_creation = 0;
1286 if(NETDATA.options.debug.chart_errors) {
1287 this.library.create(this, data);
1288 this.created_ms = new Date().getTime();
1292 this.library.create(this, data);
1293 this.created_ms = new Date().getTime();
1296 this.error('chart "' + this.id + '" failed to be created as ' + this.library_name);
1300 this.legendShowLatestValues();
1302 // update the performance counters
1303 var now = new Date().getTime();
1305 // don't update last_autorefreshed if this chart is
1306 // forced to be updated with global PanAndZoom
1307 if(NETDATA.globalPanAndZoom.isActive())
1308 this.current.last_autorefreshed = 0;
1310 this.current.last_autorefreshed = now;
1312 this.refresh_dt_ms = now - started;
1313 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
1315 if(this.refresh_dt_element)
1316 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
1319 chartState.prototype.updateChart = function(callback) {
1320 // due to late initialization of charts and libraries
1321 // we need to check this too
1322 if(this.enabled === false) {
1323 if(this.debug) this.log('I am not enabled');
1324 if(typeof callback === 'function') callback();
1328 if(this.chart === null) {
1330 this.getChart(function() { self.updateChart(callback); });
1334 if(this.library.initialized === false) {
1336 this.library.initialize(function() { self.updateChart(callback); });
1340 this.clearSelection();
1343 if(this.debug) this.log('updating from ' + this.current.url);
1346 this.xhr = $.ajax( {
1347 url: this.current.url,
1348 crossDomain: NETDATA.options.crossDomainAjax,
1352 .success(function(data) {
1354 if(self.debug) self.log('data received. updating chart.');
1355 self.updateChartWithData(data);
1359 self.error('data download failed for url: ' + self.current.url);
1361 .always(function() {
1363 if(typeof callback === 'function') callback();
1367 chartState.prototype.unhideChart = function() {
1368 if(typeof this.___isHidden___ !== 'undefined') {
1369 this.element_message.style.display = 'none';
1370 if(this.element_chart !== null) this.element_chart.style.display = 'inline-block';
1371 if(this.element_legend !== null) this.element_legend.style.display = 'inline-block';
1372 if(this.element_loading !== null) this.element_loading.style.display = 'none';
1373 this.___isHidden___ = undefined;
1374 this.element_message.innerHTML = 'chart ' + this.id + ' is visible now';
1378 chartState.prototype.hideChart = function() {
1379 if(typeof this.___isHidden___ === 'undefined') {
1380 this.element_message.style.display = 'inline-block';
1381 if(this.element_chart !== null) this.element_chart.style.display = 'none';
1382 if(this.element_legend !== null) this.element_legend.style.display = 'none';
1383 if(this.element_loading !== null) this.element_loading.style.display = 'none';
1384 this.___isHidden___ = true;
1385 this.element_message.innerHTML = 'chart ' + this.id + ' is hidden to speed up the browser';
1389 chartState.prototype.hideLoading = function() {
1390 if(typeof this.___showsLoading___ !== 'undefined') {
1391 this.element_message.style.display = 'none';
1392 if(this.element_chart !== null) this.element_chart.style.display = 'inline-block';
1393 if(this.element_legend !== null) this.element_legend.style.display = 'inline-block';
1394 if(this.element_loading !== null) this.element_loading.style.display = 'none';
1395 this.___showsLoading___ = undefined;
1396 this.element_loading.innerHTML = 'chart ' + this.id + ' finished loading!';
1400 chartState.prototype.showLoading = function() {
1401 if(typeof this.___showsLoading___ === 'undefined' && this.created_ms === 0) {
1402 this.element_message.style.display = 'none';
1403 if(this.element_chart !== null) this.element_chart.style.display = 'none';
1404 if(this.element_legend !== null) this.element_legend.style.display = 'none';
1405 if(this.element_loading !== null) this.element_loading.style.display = 'inline-block';
1406 this.___showsLoading___ = true;
1407 this.element_loading.innerHTML = 'chart ' + this.id + ' is loading...';
1411 chartState.prototype.isVisible = function() {
1412 var wh = window.innerHeight;
1413 var x = this.element.getBoundingClientRect();
1418 if(x.top < 0 && -x.top > x.height) {
1419 // the chart is entirely above
1420 ret = -x.top - x.height;
1422 else if(x.top > wh) {
1423 // the chart is entirely below
1427 if(ret > tolerance) {
1428 // the chart is too far
1429 if(this.created_ms !== 0) this.hideChart();
1433 // the chart is inside or very close
1438 chartState.prototype.isAutoRefreshed = function() {
1439 return (this.current.autorefresh);
1442 chartState.prototype.canBeAutoRefreshed = function() {
1443 now = new Date().getTime();
1445 if(this.enabled === false) {
1446 if(this.debug) this.log('I am not enabled');
1450 if(this.library === null || this.library.enabled === false) {
1451 this.error('charting library "' + this.library_name + '" is not available');
1452 if(this.debug) this.log('My chart library ' + this.library_name + ' is not available');
1456 if(this.isVisible() === false) {
1457 if(NETDATA.options.debug.visibility || this.debug) this.log('I am not visible');
1461 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
1462 if(this.debug) this.log('timed force update detecting - allowing this update');
1463 this.current.force_update_at = 0;
1467 if(this.isAutoRefreshed()) {
1468 // allow the first update, even if the page is not visible
1469 if(this.updates_counter && !NETDATA.options.page_is_visible) {
1470 if(NETDATA.options.debug.focus || this.debug) this.log('canBeAutoRefreshed(): page does not have focus');
1474 // options valid only for autoRefresh()
1475 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
1476 if(NETDATA.globalPanAndZoom.isActive()) {
1477 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
1478 if(this.debug) this.log('canBeAutoRefreshed(): global panning: I need an update.');
1482 if(this.debug) this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
1488 if(this.debug) this.log('canBeAutoRefreshed(): I have a selection in place.');
1493 if(this.debug) this.log('canBeAutoRefreshed(): I am paused.');
1497 if(now - this.current.last_autorefreshed > this.current.view_update_every) {
1498 if(this.debug) this.log('canBeAutoRefreshed(): It is time to update me.');
1507 chartState.prototype.autoRefresh = function(callback) {
1508 if(this.canBeAutoRefreshed()) {
1509 this.updateChart(callback);
1512 if(typeof callback !== 'undefined')
1517 chartState.prototype._defaultsFromDownloadedChart = function(chart) {
1519 this.chart_url = chart.url;
1520 this.current.view_update_every = chart.update_every * 1000;
1521 this.current.points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
1524 // fetch the chart description from the netdata server
1525 chartState.prototype.getChart = function(callback) {
1526 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
1528 this._defaultsFromDownloadedChart(this.chart);
1529 if(typeof callback === 'function') callback();
1532 this.chart_url = this.host + "/api/v1/chart?chart=" + this.id;
1533 if(this.debug) this.log('downloading ' + this.chart_url);
1537 url: this.chart_url,
1538 crossDomain: NETDATA.options.crossDomainAjax,
1542 .done(function(chart) {
1543 chart.url = self.chart_url;
1544 chart.data_url = (self.host + chart.data_url);
1545 self._defaultsFromDownloadedChart(chart);
1546 NETDATA.chartRegistry.add(self.host, self.id, chart);
1549 NETDATA.error(404, self.chart_url);
1550 self.error('chart "' + self.id + '" not found on url "' + self.chart_url + '"');
1552 .always(function() {
1553 if(typeof callback === 'function') callback();
1558 // resize the chart to its real dimensions
1559 // as given by the caller
1560 chartState.prototype.sizeChart = function() {
1561 this.element.className += " netdata-container";
1563 if(this.debug) this.log('sizing element');
1566 $(this.element).css('width', this.width);
1569 $(this.element).css('height', this.height);
1571 if(NETDATA.chartDefaults.min_width)
1572 $(this.element).css('min-width', NETDATA.chartDefaults.min_width);
1575 // show a message in the chart
1576 chartState.prototype.message = function(type, msg) {
1577 this.element_message.innerHTML = msg;
1580 // reset the creation datetime
1581 // since we overwrote the whole element
1582 if(this.debug) this.log(msg);
1585 // show an error on the chart and stop it forever
1586 chartState.prototype.error = function(msg) {
1587 this.message('error', this.id + ': ' + msg);
1588 this.enabled = false;
1591 // show a message indicating the chart is loading
1592 chartState.prototype.info = function(msg) {
1593 this.message('info', this.id + ': ' + msg);
1596 chartState.prototype.init = function() {
1597 this.element.innerHTML = '';
1599 this.element_message = document.createElement('div');
1600 this.element_message.className += ' netdata-message';
1601 this.element.appendChild(this.element_message);
1603 this.element_loading = document.createElement('div');
1604 this.element_loading.className += ' netdata-chart-is-loading';
1605 this.element.appendChild(this.element_loading);
1607 if(this.debug) this.log('created');
1610 // make sure the host does not end with /
1611 // all netdata API requests use absolute paths
1612 while(this.host.slice(-1) === '/')
1613 this.host = this.host.substring(0, this.host.length - 1);
1615 // check the requested library is available
1616 // we don't initialize it here - it will be initialized when
1617 // this chart will be first used
1618 if(typeof NETDATA.chartLibraries[this.library_name] === 'undefined') {
1619 NETDATA.error(402, this.library_name);
1620 this.error('chart library "' + this.library_name + '" is not found');
1622 else if(!NETDATA.chartLibraries[this.library_name].enabled) {
1623 NETDATA.error(403, this.library_name);
1624 this.error('chart library "' + this.library_name + '" is not enabled');
1627 this.library = NETDATA.chartLibraries[this.library_name];
1629 // if we need to report the rendering speed
1630 // find the element that needs to be updated
1631 if(this.refresh_dt_element_name)
1632 this.refresh_dt_element = document.getElementById(this.refresh_dt_element_name) || null;
1634 // the default mode for all charts
1635 this.setMode('auto');
1638 // get or create a chart state, given a DOM element
1639 NETDATA.chartState = function(element) {
1640 var state = $(element).data('netdata-state-object') || null;
1642 state = new chartState(element);
1643 $(element).data('netdata-state-object', state);
1648 // ----------------------------------------------------------------------------------------------------------------
1649 // Library functions
1651 // Load a script without jquery
1652 // This is used to load jquery - after it is loaded, we use jquery
1653 NETDATA._loadjQuery = function(callback) {
1654 if(typeof jQuery === 'undefined') {
1655 if(NETDATA.options.debug.main_loop) console.log('loading ' + NETDATA.jQuery);
1657 var script = document.createElement('script');
1658 script.type = 'text/javascript';
1659 script.async = true;
1660 script.src = NETDATA.jQuery;
1662 // script.onabort = onError;
1663 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
1664 if(typeof callback === "function")
1665 script.onload = callback;
1667 var s = document.getElementsByTagName('script')[0];
1668 s.parentNode.insertBefore(script, s);
1670 else if(typeof callback === "function")
1674 NETDATA._loadCSS = function(filename) {
1675 var fileref = document.createElement("link");
1676 fileref.setAttribute("rel", "stylesheet");
1677 fileref.setAttribute("type", "text/css");
1678 fileref.setAttribute("href", filename);
1680 if (typeof fileref !== 'undefined')
1681 document.getElementsByTagName("head")[0].appendChild(fileref);
1684 NETDATA.colorHex2Rgb = function(hex) {
1685 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
1686 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
1687 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
1688 return r + r + g + g + b + b;
1691 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
1693 r: parseInt(result[1], 16),
1694 g: parseInt(result[2], 16),
1695 b: parseInt(result[3], 16)
1699 NETDATA.colorLuminance = function(hex, lum) {
1700 // validate hex string
1701 hex = String(hex).replace(/[^0-9a-f]/gi, '');
1703 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
1707 // convert to decimal and change luminosity
1708 var rgb = "#", c, i;
1709 for (i = 0; i < 3; i++) {
1710 c = parseInt(hex.substr(i*2,2), 16);
1711 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
1712 rgb += ("00"+c).substr(c.length);
1718 NETDATA.guid = function() {
1720 return Math.floor((1 + Math.random()) * 0x10000)
1725 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
1728 NETDATA.zeropad = function(x) {
1729 if(x > -10 && x < 10) return '0' + x.toString();
1730 else return x.toString();
1733 // user function to signal us the DOM has been
1735 NETDATA.updatedDom = function() {
1736 NETDATA.options.updated_dom = true;
1739 NETDATA.ready = function(callback) {
1740 NETDATA.options.readyCallback = callback;
1743 NETDATA.pause = function(callback) {
1744 NETDATA.options.pauseCallback = callback;
1747 NETDATA.unpause = function() {
1748 NETDATA.options.pauseCallback = null;
1749 NETDATA.options.updated_dom = true;
1750 NETDATA.options.pause = false;
1753 // ----------------------------------------------------------------------------------------------------------------
1755 NETDATA.chartRefresherNoParallel = function(index) {
1756 // if(NETDATA.options.debug.mail_loop) console.log('NETDATA.chartRefresher(<targets, ' + index + ')');
1758 if(NETDATA.options.updated_dom) {
1759 // the dom has been updated
1760 // get the dom parts again
1761 NETDATA.getDomCharts(NETDATA.chartRefresher);
1764 // FIXME targets should be states not DOM elements
1765 var target = NETDATA.options.targets.get(index);
1766 if(typeof target === 'undefined') {
1767 if(NETDATA.options.debug.main_loop) console.log('waiting to restart main loop...');
1768 NETDATA.options.auto_refresher_fast_weight = 0;
1770 setTimeout(function() {
1771 NETDATA.chartRefresher();
1772 }, NETDATA.options.current.idle_between_loops);
1775 var state = NETDATA.chartState(target);
1777 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
1778 if(NETDATA.options.debug.main_loop) console.log('fast rendering...');
1780 state.autoRefresh(function() {
1781 NETDATA.chartRefresherNoParallel(++index);
1785 if(NETDATA.options.debug.main_loop) console.log('waiting for next refresh...');
1786 NETDATA.options.auto_refresher_fast_weight = 0;
1788 setTimeout(function() {
1789 state.autoRefresh(function() {
1790 NETDATA.chartRefresherNoParallel(++index);
1792 }, NETDATA.options.current.idle_between_charts);
1797 NETDATA.chartRefresher_sequencial = function() {
1798 if(NETDATA.options.updated_dom) {
1799 // the dom has been updated
1800 // get the dom parts again
1801 NETDATA.getDomCharts(NETDATA.chartRefresher);
1805 if(NETDATA.options.sequencial.length === 0)
1806 NETDATA.chartRefresher();
1808 var state = NETDATA.options.sequencial.pop();
1809 if(state.library.initialized)
1810 NETDATA.chartRefresher();
1812 state.autoRefresh(NETDATA.chartRefresher_sequencial);
1816 NETDATA.chartRefresher = function() {
1817 if(NETDATA.options.pause) {
1818 // console.log('auto-refresher is paused');
1819 setTimeout(NETDATA.chartRefresher,
1820 NETDATA.options.current.idle_between_loops);
1824 if(typeof NETDATA.options.pauseCallback === 'function') {
1825 // console.log('auto-refresher is calling pauseCallback');
1826 NETDATA.options.pause = true;
1827 NETDATA.options.pauseCallback();
1828 NETDATA.chartRefresher();
1831 if(!NETDATA.options.current.parallel_refresher) {
1832 NETDATA.chartRefresherNoParallel(0);
1836 if(NETDATA.options.updated_dom) {
1837 // the dom has been updated
1838 // get the dom parts again
1839 NETDATA.getDomCharts(NETDATA.chartRefresher);
1843 NETDATA.options.parallel = new Array();
1844 NETDATA.options.sequencial = new Array();
1846 // FIXME targets should be states not DOM elements
1847 for(var i = 0; i < NETDATA.options.targets.length ; i++) {
1848 var target = NETDATA.options.targets.get(i);
1849 var state = NETDATA.chartState(target);
1851 if(!state.library.initialized)
1852 NETDATA.options.sequencial.push(state);
1854 NETDATA.options.parallel.push(state);
1857 if(NETDATA.options.parallel.length > 0) {
1858 NETDATA.options.parallel_jobs = NETDATA.options.parallel.length;
1860 $(NETDATA.options.parallel).each(function() {
1861 this.autoRefresh(function() {
1862 NETDATA.options.parallel_jobs--;
1863 if(NETDATA.options.parallel_jobs === 0) {
1864 setTimeout(NETDATA.chartRefresher_sequencial,
1865 NETDATA.options.current.idle_between_charts);
1871 setTimeout(NETDATA.chartRefresher_sequencial,
1872 NETDATA.options.current.idle_between_charts);
1876 NETDATA.getDomCharts = function(callback) {
1877 NETDATA.options.updated_dom = false;
1879 // FIXME targets should be states not DOM elements
1880 NETDATA.options.targets = $('div[data-netdata]').filter(':visible');
1882 if(NETDATA.options.debug.main_loop)
1883 console.log('DOM updated - there are ' + NETDATA.options.targets.length + ' charts on page.');
1885 // we need to re-size all the charts quickly
1886 // before making any external calls
1887 // FIXME targets should be states not DOM elements
1888 $.each(NETDATA.options.targets, function(i, target) {
1889 // the initialization will take care of sizing
1890 // and the "loading..." message
1891 var state = NETDATA.chartState(target);
1894 if(typeof callback === 'function') callback();
1897 // this is the main function - where everything starts
1898 NETDATA.start = function() {
1899 // this should be called only once
1901 NETDATA.options.page_is_visible = true;
1903 $(window).blur(function() {
1904 NETDATA.options.page_is_visible = false;
1905 if(NETDATA.options.debug.focus) console.log('Lost Focus!');
1908 $(window).focus(function() {
1909 NETDATA.options.page_is_visible = true;
1910 if(NETDATA.options.debug.focus) console.log('Focus restored!');
1913 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
1914 NETDATA.options.page_is_visible = false;
1915 if(NETDATA.options.debug.focus) console.log('Document has no focus!');
1918 NETDATA.getDomCharts(NETDATA.chartRefresher);
1921 // ----------------------------------------------------------------------------------------------------------------
1924 NETDATA.peityInitialize = function(callback) {
1925 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
1927 url: NETDATA.peity_js,
1932 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
1935 NETDATA.error(100, NETDATA.peity_js);
1937 .always(function() {
1938 if(typeof callback === "function")
1943 NETDATA.chartLibraries.peity.enabled = false;
1944 if(typeof callback === "function")
1949 NETDATA.peityChartUpdate = function(state, data) {
1950 $(state.peity_instance).html(data.result);
1951 // $(state.element_chart).change() does not accept options
1952 // to pass width and height
1953 //$(state.peity_instance).change();
1954 $(state.peity_instance).peity('line', { width: state.chartWidth(), height: state.chartHeight() });
1957 NETDATA.peityChartCreate = function(state, data) {
1958 state.peity_instance = document.createElement('div');
1959 state.element_chart.appendChild(state.peity_instance);
1961 $(state.peity_instance).html(data.result);
1962 $(state.peity_instance).peity('line', { width: state.chartWidth(), height: state.chartHeight() });
1965 // ----------------------------------------------------------------------------------------------------------------
1968 NETDATA.sparklineInitialize = function(callback) {
1969 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
1971 url: NETDATA.sparkline_js,
1976 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
1979 NETDATA.error(100, NETDATA.sparkline_js);
1981 .always(function() {
1982 if(typeof callback === "function")
1987 NETDATA.chartLibraries.sparkline.enabled = false;
1988 if(typeof callback === "function")
1993 NETDATA.sparklineChartUpdate = function(state, data) {
1994 state.sparkline_options.width = state.chartWidth();
1995 state.sparkline_options.height = state.chartHeight();
1997 $(state.element_chart).sparkline(data.result, state.sparkline_options);
2000 NETDATA.sparklineChartCreate = function(state, data) {
2001 var self = $(state.element);
2002 var type = self.data('sparkline-type') || 'line';
2003 var lineColor = self.data('sparkline-linecolor') || NETDATA.colors[0];
2004 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?'#FFF':NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
2005 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
2006 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
2007 var composite = self.data('sparkline-composite') || undefined;
2008 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
2009 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
2010 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
2011 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
2012 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
2013 var spotColor = self.data('sparkline-spotcolor') || undefined;
2014 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
2015 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
2016 var spotRadius = self.data('sparkline-spotradius') || undefined;
2017 var valueSpots = self.data('sparkline-valuespots') || undefined;
2018 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
2019 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
2020 var lineWidth = self.data('sparkline-linewidth') || undefined;
2021 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
2022 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
2023 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
2024 var xvalues = self.data('sparkline-xvalues') || undefined;
2025 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
2026 var xvalues = self.data('sparkline-xvalues') || undefined;
2027 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
2028 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
2029 var disableInteraction = self.data('sparkline-disableinteraction') || false;
2030 var disableTooltips = self.data('sparkline-disabletooltips') || false;
2031 var disableHighlight = self.data('sparkline-disablehighlight') || false;
2032 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
2033 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
2034 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
2035 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
2036 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
2037 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
2038 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.chart.units;
2039 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
2040 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
2041 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
2042 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
2043 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
2044 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
2045 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
2046 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
2047 var animatedZooms = self.data('sparkline-animatedzooms') || false;
2049 state.sparkline_options = {
2051 lineColor: lineColor,
2052 fillColor: fillColor,
2053 chartRangeMin: chartRangeMin,
2054 chartRangeMax: chartRangeMax,
2055 composite: composite,
2056 enableTagOptions: enableTagOptions,
2057 tagOptionPrefix: tagOptionPrefix,
2058 tagValuesAttribute: tagValuesAttribute,
2059 disableHiddenCheck: disableHiddenCheck,
2060 defaultPixelsPerValue: defaultPixelsPerValue,
2061 spotColor: spotColor,
2062 minSpotColor: minSpotColor,
2063 maxSpotColor: maxSpotColor,
2064 spotRadius: spotRadius,
2065 valueSpots: valueSpots,
2066 highlightSpotColor: highlightSpotColor,
2067 highlightLineColor: highlightLineColor,
2068 lineWidth: lineWidth,
2069 normalRangeMin: normalRangeMin,
2070 normalRangeMax: normalRangeMax,
2071 drawNormalOnTop: drawNormalOnTop,
2073 chartRangeClip: chartRangeClip,
2074 chartRangeMinX: chartRangeMinX,
2075 chartRangeMaxX: chartRangeMaxX,
2076 disableInteraction: disableInteraction,
2077 disableTooltips: disableTooltips,
2078 disableHighlight: disableHighlight,
2079 highlightLighten: highlightLighten,
2080 highlightColor: highlightColor,
2081 tooltipContainer: tooltipContainer,
2082 tooltipClassname: tooltipClassname,
2083 tooltipChartTitle: state.chart.title,
2084 tooltipFormat: tooltipFormat,
2085 tooltipPrefix: tooltipPrefix,
2086 tooltipSuffix: tooltipSuffix,
2087 tooltipSkipNull: tooltipSkipNull,
2088 tooltipValueLookups: tooltipValueLookups,
2089 tooltipFormatFieldlist: tooltipFormatFieldlist,
2090 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
2091 numberFormatter: numberFormatter,
2092 numberDigitGroupSep: numberDigitGroupSep,
2093 numberDecimalMark: numberDecimalMark,
2094 numberDigitGroupCount: numberDigitGroupCount,
2095 animatedZooms: animatedZooms,
2096 width: state.chartWidth(),
2097 height: state.chartHeight()
2100 $(state.element_chart).sparkline(data.result, state.sparkline_options);
2103 // ----------------------------------------------------------------------------------------------------------------
2110 NETDATA.dygraphSetSelection = function(state, t) {
2111 if(typeof state.dygraph_instance !== 'undefined') {
2112 var r = state.calculateRowForTime(t);
2114 state.dygraph_instance.setSelection(r);
2118 state.dygraph_instance.clearSelection();
2124 NETDATA.dygraphClearSelection = function(state, t) {
2125 if(typeof state.dygraph_instance !== 'undefined') {
2126 state.dygraph_instance.clearSelection();
2131 NETDATA.dygraphSmoothInitialize = function(callback) {
2133 url: NETDATA.dygraph_smooth_js,
2138 NETDATA.dygraph.smooth = true;
2139 smoothPlotter.smoothing = 0.3;
2141 .always(function() {
2142 if(typeof callback === "function")
2147 NETDATA.dygraphInitialize = function(callback) {
2148 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
2150 url: NETDATA.dygraph_js,
2155 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
2156 NETDATA.dygraphSmoothInitialize(callback);
2159 NETDATA.error(100, NETDATA.dygraph_js);
2160 if(typeof callback === "function")
2165 NETDATA.chartLibraries.dygraph.enabled = false;
2166 if(typeof callback === "function")
2171 NETDATA.dygraphChartUpdate = function(state, data) {
2172 var dygraph = state.dygraph_instance;
2174 if(state.current.name === 'pan') {
2175 if(NETDATA.options.debug.dygraph || state.debug) state.log('dygraphChartUpdate() loose update');
2176 dygraph.updateOptions({
2177 file: data.result.data,
2178 labels: data.result.labels,
2179 labelsDivWidth: state.chartWidth() - 70
2183 if(NETDATA.options.debug.dygraph || state.debug) state.log('dygraphChartUpdate() strict update');
2184 dygraph.updateOptions({
2185 file: data.result.data,
2186 labels: data.result.labels,
2187 labelsDivWidth: state.chartWidth() - 70,
2194 NETDATA.dygraphChartCreate = function(state, data) {
2195 if(NETDATA.options.debug.dygraph || state.debug) state.log('dygraphChartCreate()');
2197 var self = $(state.element);
2199 state.dygraph_options = {
2200 colors: self.data('dygraph-colors') || NETDATA.colors,
2202 // leave a few pixels empty on the right of the chart
2203 rightGap: self.data('dygraph-rightgap') || 5,
2204 showRangeSelector: self.data('dygraph-showrangeselector') || false,
2205 showRoller: self.data('dygraph-showroller') || false,
2207 title: self.data('dygraph-title') || state.chart.title,
2208 titleHeight: self.data('dygraph-titleheight') || 19,
2210 legend: self.data('dygraph-legend') || NETDATA.chartLibraries.dygraph.legend(state)?'always':'never', // 'onmouseover',
2211 labels: data.result.labels,
2212 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
2213 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'10px', 'zIndex': 10000 },
2214 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
2215 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
2216 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
2219 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
2220 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
2222 ylabel: state.chart.units,
2223 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
2225 // the function to plot the chart
2228 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
2229 strokeWidth: self.data('dygraph-strokewidth') || (state.chart.chart_type === 'stacked')?0.0:1.0,
2230 strokePattern: self.data('dygraph-strokepattern') || undefined,
2232 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
2233 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
2234 drawPoints: self.data('dygraph-drawpoints') || false,
2236 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
2237 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
2239 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
2240 pointSize: self.data('dygraph-pointsize') || 1,
2242 // enabling this makes the chart with little square lines
2243 stepPlot: self.data('dygraph-stepplot') || false,
2245 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
2246 strokeBorderColor: self.data('dygraph-strokebordercolor') || 'white',
2247 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (state.chart.chart_type === 'stacked')?0.0:0.0,
2249 fillGraph: self.data('dygraph-fillgraph') || (state.chart.chart_type === 'area')?true:false,
2250 fillAlpha: self.data('dygraph-fillalpha') || (state.chart.chart_type === 'stacked')?0.8:0.2,
2251 stackedGraph: self.data('dygraph-stackedgraph') || (state.chart.chart_type === 'stacked')?true:false,
2252 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
2254 drawAxis: self.data('dygraph-drawaxis') || true,
2255 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
2256 axisLineColor: self.data('dygraph-axislinecolor') || '#DDD',
2257 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
2259 drawGrid: self.data('dygraph-drawgrid') || true,
2260 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
2261 drawYGrid: self.data('dygraph-drawygrid') || undefined,
2262 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
2263 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
2264 gridLineColor: self.data('dygraph-gridlinecolor') || '#EEE',
2266 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
2267 sigFigs: self.data('dygraph-sigfigs') || null,
2268 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
2269 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
2271 highlightCircleSize: self.data('dygraph-highlightcirclesize') || 4,
2272 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
2273 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (state.chart.chart_type === 'stacked')?0.7:0.5,
2275 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
2279 ticker: Dygraph.dateTicker,
2280 axisLabelFormatter: function (d, gran) {
2281 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
2283 valueFormatter: function (ms) {
2284 var d = new Date(ms);
2285 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
2286 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
2291 valueFormatter: function (x) {
2292 // return (Math.round(x*100) / 100).toLocaleString();
2293 return state.legendFormatValue(x);
2297 legendFormatter: function(data) {
2298 var g = data.dygraph;
2300 var elements = state.element_legend_childs;
2302 // if the hidden div is not there
2303 // state is not managing the legend
2304 if(elements.hidden === null) return;
2306 if (typeof data.x === 'undefined') {
2307 state.legendReset();
2310 state.legendSetDate(data.x);
2311 for (var i = 0; i < data.series.length; i++) {
2312 var series = data.series[i];
2313 if(!series.isVisible) continue;
2314 state.legendSetLabelValue(series.label, series.yHTML);
2315 // elements.series[series.label].value.innerHTML = series.yHTML;
2321 drawCallback: function(dygraph, is_initial) {
2322 if(state.current.name !== 'auto') {
2323 if(NETDATA.options.debug.dygraph) state.log('dygraphDrawCallback()');
2325 var x_range = dygraph.xAxisRange();
2326 var after = Math.round(x_range[0]);
2327 var before = Math.round(x_range[1]);
2329 state.updateChartPanOrZoom(after, before);
2332 zoomCallback: function(minDate, maxDate, yRanges) {
2333 if(NETDATA.options.debug.dygraph) state.log('dygraphZoomCallback()');
2334 state.globalSelectionSyncStop();
2335 state.globalSelectionSyncDelay();
2336 state.updateChartPanOrZoom(minDate, maxDate);
2338 highlightCallback: function(event, x, points, row, seriesName) {
2339 if(NETDATA.options.debug.dygraph || state.debug) state.log('dygraphHighlightCallback()');
2342 // there is a bug in dygraph when the chart is zoomed enough
2343 // the time it thinks is selected is wrong
2344 // here we calculate the time t based on the row number selected
2346 var t = state.current.after_ms + row * state.current.view_update_every;
2347 // console.log('row = ' + row + ', x = ' + x + ', t = ' + t + ' ' + ((t === x)?'SAME':'DIFFERENT') + ', rows in db: ' + state.current.data.points + ' visible(x) = ' + state.timeIsVisible(x) + ' visible(t) = ' + state.timeIsVisible(t) + ' r(x) = ' + state.calculateRowForTime(x) + ' r(t) = ' + state.calculateRowForTime(t) + ' range: ' + state.current.after_ms + ' - ' + state.current.before_ms + ' real: ' + state.current.data.after + ' - ' + state.current.data.before + ' every: ' + state.current.view_update_every);
2349 state.globalSelectionSync(t);
2351 // fix legend zIndex using the internal structures of dygraph legend module
2352 // this works, but it is a hack!
2353 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
2355 unhighlightCallback: function(event) {
2356 if(NETDATA.options.debug.dygraph || state.debug) state.log('dygraphUnhighlightCallback()');
2357 state.unpauseChart();
2358 state.globalSelectionSyncStop();
2360 interactionModel : {
2361 mousedown: function(event, dygraph, context) {
2362 if(NETDATA.options.debug.dygraph || state.debug) state.log('interactionModel.mousedown()');
2363 state.globalSelectionSyncStop();
2365 if(NETDATA.options.debug.dygraph) state.log('dygraphMouseDown()');
2367 // Right-click should not initiate a zoom.
2368 if(event.button && event.button === 2) return;
2370 context.initializeMouseDown(event, dygraph, context);
2372 if(event.button && event.button === 1) {
2373 if (event.altKey || event.shiftKey) {
2374 state.setMode('pan');
2375 state.globalSelectionSyncDelay();
2376 Dygraph.startPan(event, dygraph, context);
2379 state.setMode('zoom');
2380 state.globalSelectionSyncDelay();
2381 Dygraph.startZoom(event, dygraph, context);
2385 if (event.altKey || event.shiftKey) {
2386 state.setMode('zoom');
2387 state.globalSelectionSyncDelay();
2388 Dygraph.startZoom(event, dygraph, context);
2391 state.setMode('pan');
2392 state.globalSelectionSyncDelay();
2393 Dygraph.startPan(event, dygraph, context);
2397 mousemove: function(event, dygraph, context) {
2398 if(NETDATA.options.debug.dygraph || state.debug) state.log('interactionModel.mousemove()');
2400 if(context.isPanning) {
2401 state.globalSelectionSyncStop();
2402 state.globalSelectionSyncDelay();
2403 state.setMode('pan');
2404 Dygraph.movePan(event, dygraph, context);
2406 else if(context.isZooming) {
2407 state.globalSelectionSyncStop();
2408 state.globalSelectionSyncDelay();
2409 state.setMode('zoom');
2410 Dygraph.moveZoom(event, dygraph, context);
2413 mouseup: function(event, dygraph, context) {
2414 if(NETDATA.options.debug.dygraph || state.debug) state.log('interactionModel.mouseup()');
2416 if (context.isPanning) {
2417 state.globalSelectionSyncDelay();
2418 Dygraph.endPan(event, dygraph, context);
2420 else if (context.isZooming) {
2421 state.globalSelectionSyncDelay();
2422 Dygraph.endZoom(event, dygraph, context);
2425 click: function(event, dygraph, context) {
2426 if(NETDATA.options.debug.dygraph || state.debug) state.log('interactionModel.click()');
2427 /*Dygraph.cancelEvent(event);*/
2429 dblclick: function(event, dygraph, context) {
2430 if(NETDATA.options.debug.dygraph || state.debug) state.log('interactionModel.dblclick()');
2431 state.globalSelectionSyncStop();
2432 NETDATA.globalPanAndZoom.clearMaster();
2435 mousewheel: function(event, dygraph, context) {
2436 if(NETDATA.options.debug.dygraph || state.debug) state.log('interactionModel.mousewheel()');
2438 if(event.altKey || event.shiftKey) {
2439 state.globalSelectionSyncStop();
2440 state.globalSelectionSyncDelay();
2442 // http://dygraphs.com/gallery/interaction-api.js
2443 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
2444 var percentage = normal / 25;
2446 var before_old = state.current.before_ms;
2447 var after_old = state.current.after_ms;
2448 var range_old = before_old - after_old;
2450 var range = range_old * ( 1 - percentage );
2451 var dt = Math.round((range_old - range) / 2);
2453 var before = before_old - dt;
2454 var after = after_old + dt;
2456 if(NETDATA.options.debug.dygraph) state.log('percent: ' + percentage + ' from ' + after_old + ' - ' + before_old + ' to ' + after + ' - ' + before + ', range from ' + (before_old - after_old).toString() + ' to ' + (before - after).toString());
2458 state.setMode('zoom');
2459 state.updateChartPanOrZoom(after, before);
2462 touchstart: function(event, dygraph, context) {
2463 if(NETDATA.options.debug.dygraph || state.debug) state.log('interactionModel.touchstart()');
2464 state.globalSelectionSyncStop();
2465 state.globalSelectionSyncDelay();
2466 Dygraph.Interaction.startTouch(event, dygraph, context);
2467 context.touchDirections = { x: true, y: false };
2468 state.setMode('zoom');
2470 touchmove: function(event, dygraph, context) {
2471 if(NETDATA.options.debug.dygraph || state.debug) state.log('interactionModel.touchmove()');
2472 //Dygraph.cancelEvent(event);
2473 state.globalSelectionSyncStop();
2474 Dygraph.Interaction.moveTouch(event, dygraph, context);
2476 touchend: function(event, dygraph, context) {
2477 if(NETDATA.options.debug.dygraph || state.debug) state.log('interactionModel.touchend()');
2478 Dygraph.Interaction.endTouch(event, dygraph, context);
2483 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
2484 state.dygraph_options.drawGrid = false;
2485 state.dygraph_options.drawAxis = false;
2486 state.dygraph_options.title = undefined;
2487 state.dygraph_options.units = undefined;
2488 state.dygraph_options.legend = 'never'; // 'follow'
2489 state.dygraph_options.ylabel = undefined;
2490 state.dygraph_options.yLabelWidth = 0;
2491 state.dygraph_options.labelsDivWidth = 120;
2492 state.dygraph_options.labelsDivStyles.width = '120px';
2493 state.dygraph_options.labelsSeparateLines = true;
2494 state.dygraph_options.highlightCircleSize = 3;
2495 state.dygraph_options.rightGap = 0;
2496 state.dygraph_options.strokeWidth = 1.0;
2498 else if(NETDATA.dygraph.smooth && state.chart.chart_type === 'line') {
2499 // smooth lines patch
2500 state.dygraph_options.plotter = smoothPlotter;
2501 state.dygraph_options.strokeWidth = 1.5;
2506 state.dygraph_instance = new Dygraph(state.element_chart,
2507 data.result.data, state.dygraph_options);
2510 // ----------------------------------------------------------------------------------------------------------------
2513 NETDATA.morrisInitialize = function(callback) {
2514 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
2516 // morris requires raphael
2517 if(!NETDATA.chartLibraries.raphael.initialized) {
2518 if(NETDATA.chartLibraries.raphael.enabled) {
2519 NETDATA.raphaelInitialize(function() {
2520 NETDATA.morrisInitialize(callback);
2524 NETDATA.chartLibraries.morris.enabled = false;
2525 if(typeof callback === "function")
2530 NETDATA._loadCSS(NETDATA.morris_css);
2533 url: NETDATA.morris_js,
2538 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
2541 NETDATA.error(100, NETDATA.morris_js);
2543 .always(function() {
2544 if(typeof callback === "function")
2550 NETDATA.chartLibraries.morris.enabled = false;
2551 if(typeof callback === "function")
2556 NETDATA.morrisChartUpdate = function(state, data) {
2557 state.morris_instance.setData(data.result.data);
2560 NETDATA.morrisChartCreate = function(state, data) {
2562 state.morris_options = {
2563 element: state.element_chart_id,
2564 data: data.result.data,
2566 ykeys: data.dimension_names,
2567 labels: data.dimension_names,
2573 continuousLine: false,
2574 behaveLikeLine: false
2577 if(state.chart.chart_type === 'line')
2578 state.morris_instance = new Morris.Line(state.morris_options);
2580 else if(state.chart.chart_type === 'area') {
2581 state.morris_options.behaveLikeLine = true;
2582 state.morris_instance = new Morris.Area(state.morris_options);
2585 state.morris_instance = new Morris.Area(state.morris_options);
2588 // ----------------------------------------------------------------------------------------------------------------
2591 NETDATA.raphaelInitialize = function(callback) {
2592 if(typeof netdataStopRaphael === 'undefined') {
2594 url: NETDATA.raphael_js,
2599 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
2602 NETDATA.error(100, NETDATA.raphael_js);
2604 .always(function() {
2605 if(typeof callback === "function")
2610 NETDATA.chartLibraries.raphael.enabled = false;
2611 if(typeof callback === "function")
2616 NETDATA.raphaelChartUpdate = function(state, data) {
2617 $(state.element_chart).raphael(data.result, {
2618 width: state.chartWidth(),
2619 height: state.chartHeight()
2623 NETDATA.raphaelChartCreate = function(state, data) {
2624 $(state.element_chart).raphael(data.result, {
2625 width: state.chartWidth(),
2626 height: state.chartHeight()
2630 // ----------------------------------------------------------------------------------------------------------------
2633 NETDATA.googleInitialize = function(callback) {
2634 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
2636 url: NETDATA.google_js,
2641 NETDATA.registerChartLibrary('google', NETDATA.google_js);
2643 google.load('visualization', '1.1', {
2644 'packages': ['corechart', 'controls'],
2645 'callback': callback
2649 NETDATA.error(100, NETDATA.google_js);
2650 if(typeof callback === "function")
2655 NETDATA.chartLibraries.google.enabled = false;
2656 if(typeof callback === "function")
2661 NETDATA.googleChartUpdate = function(state, data) {
2662 var datatable = new google.visualization.DataTable(data.result);
2663 state.google_instance.draw(datatable, state.google_options);
2666 NETDATA.googleChartCreate = function(state, data) {
2667 var datatable = new google.visualization.DataTable(data.result);
2669 state.google_options = {
2670 // do not set width, height - the chart resizes itself
2671 //width: state.chartWidth(),
2672 //height: state.chartHeight(),
2674 title: state.chart.title,
2677 // title: "Time of Day",
2678 // format:'HH:mm:ss',
2679 viewWindowMode: 'maximized',
2690 title: state.chart.units,
2691 viewWindowMode: 'pretty',
2706 focusTarget: 'category',
2713 titlePosition: 'out',
2724 curveType: 'function',
2729 switch(state.chart.chart_type) {
2731 state.google_options.vAxis.viewWindowMode = 'maximized';
2732 state.google_instance = new google.visualization.AreaChart(state.element_chart);
2736 state.google_options.isStacked = true;
2737 state.google_options.areaOpacity = 0.85;
2738 state.google_options.vAxis.viewWindowMode = 'maximized';
2739 state.google_options.vAxis.minValue = null;
2740 state.google_options.vAxis.maxValue = null;
2741 state.google_instance = new google.visualization.AreaChart(state.element_chart);
2746 state.google_options.lineWidth = 2;
2747 state.google_instance = new google.visualization.LineChart(state.element_chart);
2751 state.google_instance.draw(datatable, state.google_options);
2754 // ----------------------------------------------------------------------------------------------------------------
2757 NETDATA.easypiechartInitialize = function(callback) {
2758 if(typeof netdataStopEasypiechart === 'undefined') {
2760 url: NETDATA.easypiechart_js,
2765 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
2768 NETDATA.error(100, NETDATA.easypiechart_js);
2770 .always(function() {
2771 if(typeof callback === "function")
2776 NETDATA.chartLibraries.easypiechart.enabled = false;
2777 if(typeof callback === "function")
2782 NETDATA.easypiechartChartUpdate = function(state, data) {
2784 state.easypiechart_instance.update();
2787 NETDATA.easypiechartChartCreate = function(state, data) {
2788 var self = $(state.element);
2793 $(state.element_chart).data('data-percent', pcent);
2794 data.element_chart.innerHTML = value.toString();
2796 state.easypiechart_instance = new EasyPieChart(state.element_chart, {
2797 barColor: self.data('easypiechart-barcolor') || '#ef1e25',
2798 trackColor: self.data('easypiechart-trackcolor') || '#f2f2f2',
2799 scaleColor: self.data('easypiechart-scalecolor') || '#dfe0e0',
2800 scaleLength: self.data('easypiechart-scalelength') || 5,
2801 lineCap: self.data('easypiechart-linecap') || 'round',
2802 lineWidth: self.data('easypiechart-linewidth') || 3,
2803 trackWidth: self.data('easypiechart-trackwidth') || undefined,
2804 size: self.data('easypiechart-size') || Math.min(state.chartWidth(), state.chartHeight()),
2805 rotate: self.data('easypiechart-rotate') || 0,
2806 animate: self.data('easypiechart-rotate') || {duration: 0, enabled: false},
2807 easing: self.data('easypiechart-easing') || undefined
2811 // ----------------------------------------------------------------------------------------------------------------
2812 // Charts Libraries Registration
2814 NETDATA.chartLibraries = {
2816 initialize: NETDATA.dygraphInitialize,
2817 create: NETDATA.dygraphChartCreate,
2818 update: NETDATA.dygraphChartUpdate,
2819 setSelection: NETDATA.dygraphSetSelection,
2820 clearSelection: NETDATA.dygraphClearSelection,
2823 format: function(state) { return 'json'; },
2824 options: function(state) { return 'ms|flip'; },
2825 legend: function(state) {
2826 if(!this.isSparkline(state))
2827 return 'right-side';
2831 autoresize: function(state) { return true; },
2832 max_updates_to_recreate: function(state) { return 5000; },
2833 pixels_per_point: function(state) {
2834 if(!this.isSparkline(state))
2840 isSparkline: function(state) {
2841 if(typeof state.dygraph_sparkline === 'undefined') {
2842 var t = $(state.element).data('dygraph-theme');
2843 if(t === 'sparkline')
2844 state.dygraph_sparkline = true;
2846 state.dygraph_sparkline = false;
2848 return state.dygraph_sparkline;
2852 initialize: NETDATA.sparklineInitialize,
2853 create: NETDATA.sparklineChartCreate,
2854 update: NETDATA.sparklineChartUpdate,
2856 clearSelection: null,
2859 format: function(state) { return 'array'; },
2860 options: function(state) { return 'flip|abs'; },
2861 legend: function(state) { return null; },
2862 autoresize: function(state) { return false; },
2863 max_updates_to_recreate: function(state) { return 5000; },
2864 pixels_per_point: function(state) { return 3; }
2867 initialize: NETDATA.peityInitialize,
2868 create: NETDATA.peityChartCreate,
2869 update: NETDATA.peityChartUpdate,
2871 clearSelection: null,
2874 format: function(state) { return 'ssvcomma'; },
2875 options: function(state) { return 'null2zero|flip|abs'; },
2876 legend: function(state) { return null; },
2877 autoresize: function(state) { return false; },
2878 max_updates_to_recreate: function(state) { return 5000; },
2879 pixels_per_point: function(state) { return 3; }
2882 initialize: NETDATA.morrisInitialize,
2883 create: NETDATA.morrisChartCreate,
2884 update: NETDATA.morrisChartUpdate,
2886 clearSelection: null,
2889 format: function(state) { return 'json'; },
2890 options: function(state) { return 'objectrows|ms'; },
2891 legend: function(state) { return null; },
2892 autoresize: function(state) { return false; },
2893 max_updates_to_recreate: function(state) { return 50; },
2894 pixels_per_point: function(state) { return 15; }
2897 initialize: NETDATA.googleInitialize,
2898 create: NETDATA.googleChartCreate,
2899 update: NETDATA.googleChartUpdate,
2901 clearSelection: null,
2904 format: function(state) { return 'datatable'; },
2905 options: function(state) { return ''; },
2906 legend: function(state) { return null; },
2907 autoresize: function(state) { return false; },
2908 max_updates_to_recreate: function(state) { return 300; },
2909 pixels_per_point: function(state) { return 4; }
2912 initialize: NETDATA.raphaelInitialize,
2913 create: NETDATA.raphaelChartCreate,
2914 update: NETDATA.raphaelChartUpdate,
2916 clearSelection: null,
2919 format: function(state) { return 'json'; },
2920 options: function(state) { return ''; },
2921 legend: function(state) { return null; },
2922 autoresize: function(state) { return false; },
2923 max_updates_to_recreate: function(state) { return 5000; },
2924 pixels_per_point: function(state) { return 3; }
2927 initialize: NETDATA.easypiechartInitialize,
2928 create: NETDATA.easypiechartChartCreate,
2929 update: NETDATA.easypiechartChartUpdate,
2931 clearSelection: null,
2934 format: function(state) { return 'json'; },
2935 options: function(state) { return ''; },
2936 legend: function(state) { return null; },
2937 autoresize: function(state) { return false; },
2938 max_updates_to_recreate: function(state) { return 5000; },
2939 pixels_per_point: function(state) { return 3; }
2943 NETDATA.registerChartLibrary = function(library, url) {
2944 if(NETDATA.options.debug.libraries)
2945 console.log("registering chart library: " + library);
2947 NETDATA.chartLibraries[library].url = url;
2948 NETDATA.chartLibraries[library].initialized = true;
2949 NETDATA.chartLibraries[library].enabled = true;
2952 // ----------------------------------------------------------------------------------------------------------------
2955 NETDATA.requiredJs = [
2956 NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
2957 NETDATA.serverDefault + 'lib/bootstrap.min.js'
2960 NETDATA.loadRequiredJs = function(index, callback) {
2961 if(index >= NETDATA.requiredJs.length) {
2962 if(typeof callback === 'function')
2967 if(NETDATA.options.debug.main_loop) console.log('loading ' + NETDATA.requiredJs[index]);
2969 url: NETDATA.requiredJs[index],
2973 .success(function() {
2974 if(NETDATA.options.debug.main_loop) console.log('loaded ' + NETDATA.requiredJs[index]);
2975 NETDATA.loadRequiredJs(++index, callback);
2978 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index]);
2982 NETDATA.requiredCSS = [
2983 NETDATA.serverDefault + 'css/bootstrap.min.css',
2984 NETDATA.serverDefault + 'css/bootstrap-theme.min.css',
2985 NETDATA.dashboard_css
2988 NETDATA.loadRequiredCSS = function(index) {
2989 if(index >= NETDATA.requiredCSS.length) {
2990 if(typeof callback === 'function')
2995 if(NETDATA.options.debug.main_loop) console.log('loading ' + NETDATA.requiredCSS[index]);
2996 NETDATA._loadCSS(NETDATA.requiredCSS[index]);
2997 NETDATA.loadRequiredCSS(++index);
3000 NETDATA.errorReset();
3001 NETDATA._loadjQuery(function() {
3002 NETDATA.loadRequiredJs(0, function() {
3003 //NETDATA.chartRegistry.downloadAll(NETDATA.serverDefault, function() {
3004 NETDATA.loadRequiredCSS(0);
3005 // NETDATA._loadCSS(NETDATA.dashboard_css);
3006 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
3007 if(NETDATA.options.debug.main_loop) console.log('starting chart refresh thread');
3011 if(typeof NETDATA.options.readyCallback === 'function')
3012 NETDATA.options.readyCallback();
3017 // window.NETDATA = NETDATA;
3018 // })(window, document);