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 netdataNoEasyPieChart = true; // do not use easy pie chart
9 // var netdataNoGauge = true; // do not use gauge.js
10 // var netdataNoD3 = true; // do not use D3
11 // var netdataNoC3 = true; // do not use C3
12 // var netdataNoBootstrap = true; // do not load bootstrap
13 // var netdataDontStart = true; // do not start the thread to process the charts
14 // var netdataErrorCallback = null; // Callback function that will be invoked upon error
16 // You can also set the default netdata server, using the following.
17 // When this variable is not set, we assume the page is hosted on your
18 // netdata server already.
19 // var netdataServer = "http://yourhost:19999"; // set your NetData server
21 //(function(window, document, undefined) {
23 // ------------------------------------------------------------------------
24 // compatibility fixes
26 // fix IE issue with console
27 if(!window.console) { window.console = { log: function(){} }; }
29 // if string.endsWith is not defined, define it
30 if(typeof String.prototype.endsWith !== 'function') {
31 String.prototype.endsWith = function(s) {
32 if(s.length > this.length) return false;
33 return this.slice(-s.length) === s;
37 // if string.startsWith is not defined, define it
38 if(typeof String.prototype.startsWith !== 'function') {
39 String.prototype.startsWith = function(s) {
40 if(s.length > this.length) return false;
41 return this.slice(s.length) === s;
46 var NETDATA = window.NETDATA || {};
48 // ----------------------------------------------------------------------------------------------------------------
49 // Detect the netdata server
51 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
52 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
53 NETDATA._scriptSource = function() {
56 if(typeof document.currentScript !== 'undefined') {
57 script = document.currentScript;
60 var all_scripts = document.getElementsByTagName('script');
61 script = all_scripts[all_scripts.length - 1];
64 if (typeof script.getAttribute.length !== 'undefined')
67 script = script.getAttribute('src', -1);
72 if(typeof netdataServer !== 'undefined')
73 NETDATA.serverDefault = netdataServer;
75 var s = NETDATA._scriptSource();
76 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
78 console.log('WARNING: Cannot detect the URL of the netdata server.');
79 NETDATA.serverDefault = null;
83 if(NETDATA.serverDefault === null)
84 NETDATA.serverDefault = '';
85 else if(NETDATA.serverDefault.slice(-1) !== '/')
86 NETDATA.serverDefault += '/';
88 // default URLs for all the external files we need
89 // make them RELATIVE so that the whole thing can also be
90 // installed under a web server
91 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-1.12.0.min.js';
92 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity.min.js';
93 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline.min.js';
94 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart.min.js';
95 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge.min.js';
96 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined.js';
97 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter.js';
98 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-min.js';
99 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris.min.js';
100 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3.min.js';
101 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3.min.js';
102 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3.min.css';
103 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris.css';
104 NETDATA.google_js = 'https://www.google.com/jsapi';
108 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.min.css',
109 dashboard_css: NETDATA.serverDefault + 'dashboard.css',
110 background: '#FFFFFF',
111 foreground: '#000000',
114 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
115 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
116 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
117 '#329262', '#3B3EAC' ],
118 easypiechart_track: '#f0f0f0',
119 easypiechart_scale: '#dfe0e0',
120 gauge_pointer: '#C0C0C0',
121 gauge_stroke: '#F0F0F0',
125 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.slate.min.css',
126 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css',
127 background: '#272b30',
128 foreground: '#C8C8C8',
131 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
132 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
133 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
134 '#a6a479', '#a66da8' ],
136 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
137 '#3B3EAC', '#EE9911', '#BB44CC', '#C83E3E', '#990099', '#CC7700',
138 '#22AA99', '#109618', '#6633CC', '#DD4477', '#316395', '#8B0707',
139 '#329262', '#3B3EFF' ],
140 easypiechart_track: '#373b40',
141 easypiechart_scale: '#373b40',
142 gauge_pointer: '#474b50',
143 gauge_stroke: '#373b40',
144 gauge_gradient: false
148 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
149 NETDATA.themes.current = NETDATA.themes[netdataTheme];
151 NETDATA.themes.current = NETDATA.themes.white;
153 NETDATA.colors = NETDATA.themes.current.colors;
155 // these are the colors Google Charts are using
156 // we have them here to attempt emulate their look and feel on the other chart libraries
157 // http://there4.io/2012/05/02/google-chart-color-list/
158 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
159 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
160 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
162 // an alternative set
163 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
164 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
165 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
167 // ----------------------------------------------------------------------------------------------------------------
168 // the defaults for all charts
170 // if the user does not specify any of these, the following will be used
172 NETDATA.chartDefaults = {
173 host: NETDATA.serverDefault, // the server to get data from
174 width: '100%', // the chart width - can be null
175 height: '100%', // the chart height - can be null
176 min_width: null, // the chart minimum width - can be null
177 library: 'dygraph', // the graphing library to use
178 method: 'average', // the grouping method
179 before: 0, // panning
180 after: -600, // panning
181 pixels_per_point: 1, // the detail of the chart
182 fill_luminance: 0.8 // luminance of colors in solit areas
185 // ----------------------------------------------------------------------------------------------------------------
189 pauseCallback: null, // a callback when we are really paused
191 pause: false, // when enabled we don't auto-refresh the charts
193 targets: null, // an array of all the state objects that are
194 // currently active (independently of their
195 // viewport visibility)
197 updated_dom: true, // when true, the DOM has been updated with
198 // new elements we have to check.
200 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
201 // rendering charts continiously.
202 // used with .current.fast_render_timeframe
204 page_is_visible: true, // when true, this page is visible
206 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
207 // auto-refresher for some time (when a chart is
208 // performing pan or zoom, we need to stop refreshing
209 // all other charts, to have the maximum speed for
210 // rendering the chart that is panned or zoomed).
211 // Used with .current.global_pan_sync_time
213 last_resized: new Date().getTime(), // the timestamp of the last resize request
215 last_page_scroll: 0, // the timestamp the last time the page was scrolled
217 // the current profile
218 // we may have many...
220 pixels_per_point: 1, // the minimum pixels per point for all charts
221 // increase this to speed javascript up
222 // each chart library has its own limit too
223 // the max of this and the chart library is used
224 // the final is calculated every time, so a change
225 // here will have immediate effect on the next chart
228 idle_between_charts: 100, // ms - how much time to wait between chart updates
230 fast_render_timeframe: 200, // ms - render continously until this time of continious
231 // rendering has been reached
232 // this setting is used to make it render e.g. 10
233 // charts at once, sleep idle_between_charts time
234 // and continue for another 10 charts.
236 idle_between_loops: 500, // ms - if all charts have been updated, wait this
237 // time before starting again.
239 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
241 idle_lost_focus: 500, // ms - when the window does not have focus, check
242 // if focus has been regained, every this time
244 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
245 // autorefreshing of charts is paused for this amount
248 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
249 // of time before setting up synchronized selections
252 sync_selection: true, // enable or disable selection sync
254 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
256 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
258 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
260 update_only_visible: true, // enable or disable visibility management
262 parallel_refresher: true, // enable parallel refresh of charts
264 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
266 destroy_on_hide: false, // destroy charts when they are not visible
268 show_help: true, // when enabled the charts will show some help
269 show_help_delay_show_ms: 500,
270 show_help_delay_hide_ms: 0,
272 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
274 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
275 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
277 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
279 smooth_plot: true, // enable smooth plot, where possible
281 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
283 color_fill_opacity_line: 1.0,
284 color_fill_opacity_area: 0.2,
285 color_fill_opacity_stacked: 0.8,
287 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
288 pan_and_zoom_factor_multiplier_control: 2.0,
289 pan_and_zoom_factor_multiplier_shift: 3.0,
290 pan_and_zoom_factor_multiplier_alt: 4.0,
292 setOptionCallback: function() { ; }
300 chart_data_url: false,
301 chart_errors: false, // FIXME
309 NETDATA.statistics = {
312 refreshes_active_max: 0
316 // ----------------------------------------------------------------------------------------------------------------
317 // local storage options
319 NETDATA.localStorage = {
322 callback: {} // only used for resetting back to defaults
325 NETDATA.localStorageGet = function(key, def, callback) {
328 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
329 NETDATA.localStorage.default[key.toString()] = def;
330 NETDATA.localStorage.callback[key.toString()] = callback;
333 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
335 // console.log('localStorage: loading "' + key.toString() + '"');
336 ret = localStorage.getItem(key.toString());
337 if(ret === null || ret === 'undefined') {
338 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
339 localStorage.setItem(key.toString(), JSON.stringify(def));
343 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
344 ret = JSON.parse(ret);
345 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
349 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
354 if(typeof ret === 'undefined' || ret === 'undefined') {
355 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
359 NETDATA.localStorage.current[key.toString()] = ret;
363 NETDATA.localStorageSet = function(key, value, callback) {
364 if(typeof value === 'undefined' || value === 'undefined') {
365 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
368 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
369 NETDATA.localStorage.default[key.toString()] = value;
370 NETDATA.localStorage.current[key.toString()] = value;
371 NETDATA.localStorage.callback[key.toString()] = callback;
374 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
375 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
377 localStorage.setItem(key.toString(), JSON.stringify(value));
380 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
384 NETDATA.localStorage.current[key.toString()] = value;
388 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
390 if(typeof obj[i] === 'object') {
391 //console.log('object ' + prefix + '.' + i.toString());
392 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
396 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
400 NETDATA.setOption = function(key, value) {
401 if(key.toString() === 'setOptionCallback') {
402 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
403 NETDATA.options.current[key.toString()] = value;
404 NETDATA.options.current.setOptionCallback();
407 else if(NETDATA.options.current[key.toString()] !== value) {
408 var name = 'options.' + key.toString();
410 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
411 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
413 //console.log(NETDATA.localStorage);
414 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
415 //console.log(NETDATA.options);
416 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
418 if(typeof NETDATA.options.current.setOptionCallback === 'function')
419 NETDATA.options.current.setOptionCallback();
425 NETDATA.getOption = function(key) {
426 return NETDATA.options.current[key.toString()];
429 // read settings from local storage
430 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
432 // always start with this option enabled.
433 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
435 NETDATA.resetOptions = function() {
436 for(var i in NETDATA.localStorage.default) {
437 var a = i.split('.');
439 if(a[0] === 'options') {
440 if(a[1] === 'setOptionCallback') continue;
441 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
442 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
444 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
446 else if(a[0] === 'chart_heights') {
447 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
448 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
454 // ----------------------------------------------------------------------------------------------------------------
456 if(NETDATA.options.debug.main_loop === true)
457 console.log('welcome to NETDATA');
459 NETDATA.onresize = function() {
460 NETDATA.options.last_resized = new Date().getTime();
464 NETDATA.onscroll = function() {
465 // console.log('onscroll');
467 NETDATA.options.last_page_scroll = new Date().getTime();
468 if(NETDATA.options.targets === null) return;
470 // when the user scrolls he sees that we have
471 // hidden all the not-visible charts
472 // using this little function we try to switch
473 // the charts back to visible quickly
474 var targets = NETDATA.options.targets;
475 var len = targets.length;
476 while(len--) targets[len].isVisible();
479 window.onresize = NETDATA.onresize;
480 window.onscroll = NETDATA.onscroll;
482 // ----------------------------------------------------------------------------------------------------------------
485 NETDATA.errorCodes = {
486 100: { message: "Cannot load chart library", alert: true },
487 101: { message: "Cannot load jQuery", alert: true },
488 402: { message: "Chart library not found", alert: false },
489 403: { message: "Chart library not enabled/is failed", alert: false },
490 404: { message: "Chart not found", alert: false },
491 405: { message: "Cannot download charts index from server", alert: true },
492 406: { message: "Invalid charts index downloaded from server", alert: true }
494 NETDATA.errorLast = {
500 NETDATA.error = function(code, msg) {
501 NETDATA.errorLast.code = code;
502 NETDATA.errorLast.message = msg;
503 NETDATA.errorLast.datetime = new Date().getTime();
505 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
508 if(typeof netdataErrorCallback === 'function') {
509 ret = netdataErrorCallback('system', code, msg);
512 if(ret && NETDATA.errorCodes[code].alert)
513 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
516 NETDATA.errorReset = function() {
517 NETDATA.errorLast.code = 0;
518 NETDATA.errorLast.message = "You are doing fine!";
519 NETDATA.errorLast.datetime = 0;
522 // ----------------------------------------------------------------------------------------------------------------
525 // When multiple charts need the same chart, we avoid downloading it
526 // multiple times (and having it in browser memory multiple time)
527 // by using this registry.
529 // Every time we download a chart definition, we save it here with .add()
530 // Then we try to get it back with .get(). If that fails, we download it.
532 NETDATA.chartRegistry = {
535 fixid: function(id) {
536 return id.replace(/:/g, "_").replace(/\//g, "_");
539 add: function(host, id, data) {
540 host = this.fixid(host);
543 if(typeof this.charts[host] === 'undefined')
544 this.charts[host] = {};
546 //console.log('added ' + host + '/' + id);
547 this.charts[host][id] = data;
550 get: function(host, id) {
551 host = this.fixid(host);
554 if(typeof this.charts[host] === 'undefined')
557 if(typeof this.charts[host][id] === 'undefined')
560 //console.log('cached ' + host + '/' + id);
561 return this.charts[host][id];
564 downloadAll: function(host, callback) {
565 while(host.slice(-1) === '/')
566 host = host.substring(0, host.length - 1);
571 url: host + '/api/v1/charts',
575 .done(function(data) {
577 var h = NETDATA.chartRegistry.fixid(host);
578 self.charts[h] = data.charts;
580 else NETDATA.error(406, host + '/api/v1/charts');
582 if(typeof callback === 'function')
586 NETDATA.error(405, host + '/api/v1/charts');
588 if(typeof callback === 'function')
594 // ----------------------------------------------------------------------------------------------------------------
595 // Global Pan and Zoom on charts
597 // Using this structure are synchronize all the charts, so that
598 // when you pan or zoom one, all others are automatically refreshed
599 // to the same timespan.
601 NETDATA.globalPanAndZoom = {
602 seq: 0, // timestamp ms
603 // every time a chart is panned or zoomed
604 // we set the timestamp here
605 // then we use it as a sequence number
606 // to find if other charts are syncronized
609 master: null, // the master chart (state), to which all others
612 force_before_ms: null, // the timespan to sync all other charts
613 force_after_ms: null,
616 setMaster: function(state, after, before) {
617 if(NETDATA.options.current.sync_pan_and_zoom === false)
620 if(this.master !== null && this.master !== state)
621 this.master.resetChart(true, true);
623 var now = new Date().getTime();
626 this.force_after_ms = after;
627 this.force_before_ms = before;
628 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
632 clearMaster: function() {
633 if(this.master !== null) {
634 var st = this.master;
641 this.force_after_ms = null;
642 this.force_before_ms = null;
643 NETDATA.options.auto_refresher_stop_until = 0;
646 // is the given state the master of the global
647 // pan and zoom sync?
648 isMaster: function(state) {
649 if(this.master === state) return true;
653 // are we currently have a global pan and zoom sync?
654 isActive: function() {
655 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
659 // check if a chart, other than the master
660 // needs to be refreshed, due to the global pan and zoom
661 shouldBeAutoRefreshed: function(state) {
662 if(this.master === null || this.seq === 0)
665 //if(state.needsRecreation())
668 if(state.tm.pan_and_zoom_seq === this.seq)
675 // ----------------------------------------------------------------------------------------------------------------
676 // dimensions selection
679 // move color assignment to dimensions, here
681 dimensionStatus = function(parent, label, name_div, value_div, color) {
682 this.enabled = false;
683 this.parent = parent;
685 this.name_div = null;
686 this.value_div = null;
687 this.color = NETDATA.themes.current.foreground;
689 if(parent.selected_count > parent.unselected_count)
690 this.selected = true;
692 this.selected = false;
694 this.setOptions(name_div, value_div, color);
697 dimensionStatus.prototype.invalidate = function() {
698 this.name_div = null;
699 this.value_div = null;
700 this.enabled = false;
703 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
706 if(this.name_div != name_div) {
707 this.name_div = name_div;
708 this.name_div.title = this.label;
709 this.name_div.style.color = this.color;
710 if(this.selected === false)
711 this.name_div.className = 'netdata-legend-name not-selected';
713 this.name_div.className = 'netdata-legend-name selected';
716 if(this.value_div != value_div) {
717 this.value_div = value_div;
718 this.value_div.title = this.label;
719 this.value_div.style.color = this.color;
720 if(this.selected === false)
721 this.value_div.className = 'netdata-legend-value not-selected';
723 this.value_div.className = 'netdata-legend-value selected';
730 dimensionStatus.prototype.setHandler = function() {
731 if(this.enabled === false) return;
735 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
736 this.name_div.onclick = this.value_div.onclick = function(e) {
738 if(ds.isSelected()) {
740 if(e.shiftKey === true || e.ctrlKey === true) {
741 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
744 if(ds.parent.countSelected() === 0)
745 ds.parent.selectAll();
748 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
749 if(ds.parent.countSelected() === 1) {
750 ds.parent.selectAll();
753 ds.parent.selectNone();
759 // this is not selected
760 if(e.shiftKey === true || e.ctrlKey === true) {
761 // control or shift key is pressed -> select this too
765 // no key is pressed -> select only this
766 ds.parent.selectNone();
771 ds.parent.state.redrawChart();
775 dimensionStatus.prototype.select = function() {
776 if(this.enabled === false) return;
778 this.name_div.className = 'netdata-legend-name selected';
779 this.value_div.className = 'netdata-legend-value selected';
780 this.selected = true;
783 dimensionStatus.prototype.unselect = function() {
784 if(this.enabled === false) return;
786 this.name_div.className = 'netdata-legend-name not-selected';
787 this.value_div.className = 'netdata-legend-value hidden';
788 this.selected = false;
791 dimensionStatus.prototype.isSelected = function() {
792 return(this.enabled === true && this.selected === true);
795 // ----------------------------------------------------------------------------------------------------------------
797 dimensionsVisibility = function(state) {
800 this.dimensions = {};
801 this.selected_count = 0;
802 this.unselected_count = 0;
805 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
806 if(typeof this.dimensions[label] === 'undefined') {
808 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
811 this.dimensions[label].setOptions(name_div, value_div, color);
813 return this.dimensions[label];
816 dimensionsVisibility.prototype.dimensionGet = function(label) {
817 return this.dimensions[label];
820 dimensionsVisibility.prototype.invalidateAll = function() {
821 for(var d in this.dimensions)
822 this.dimensions[d].invalidate();
825 dimensionsVisibility.prototype.selectAll = function() {
826 for(var d in this.dimensions)
827 this.dimensions[d].select();
830 dimensionsVisibility.prototype.countSelected = function() {
832 for(var d in this.dimensions)
833 if(this.dimensions[d].isSelected()) i++;
838 dimensionsVisibility.prototype.selectNone = function() {
839 for(var d in this.dimensions)
840 this.dimensions[d].unselect();
843 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
844 var ret = new Array();
845 this.selected_count = 0;
846 this.unselected_count = 0;
848 for(var i = 0, len = array.length; i < len ; i++) {
849 var ds = this.dimensions[array[i]];
850 if(typeof ds === 'undefined') {
851 // console.log(array[i] + ' is not found');
856 if(ds.isSelected()) {
858 this.selected_count++;
862 this.unselected_count++;
866 if(this.selected_count === 0 && this.unselected_count !== 0) {
868 return this.selected2BooleanArray(array);
875 // ----------------------------------------------------------------------------------------------------------------
876 // global selection sync
878 NETDATA.globalSelectionSync = {
885 if(this.state !== null)
886 this.state.globalSelectionSyncStop();
890 if(this.state !== null) {
891 this.state.globalSelectionSyncDelay();
896 // ----------------------------------------------------------------------------------------------------------------
897 // Our state object, where all per-chart values are stored
899 chartState = function(element) {
900 var self = $(element);
901 this.element = element;
904 // all private functions should use 'that', instead of 'this'
908 * show an error instead of the chart
910 var error = function(msg) {
913 if(typeof netdataErrorCallback === 'function') {
914 ret = netdataErrorCallback('chart', that.id, msg);
918 that.element.innerHTML = that.id + ': ' + msg;
919 that.enabled = false;
920 that.current = that.pan;
924 // GUID - a unique identifier for the chart
925 this.uuid = NETDATA.guid();
927 // string - the name of chart
928 this.id = self.data('netdata');
930 // string - the key for localStorage settings
931 this.settings_id = self.data('id') || null;
933 // the user given dimensions of the element
934 this.width = self.data('width') || NETDATA.chartDefaults.width;
935 this.height = self.data('height') || NETDATA.chartDefaults.height;
937 if(this.settings_id !== null) {
938 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
939 // this is the callback that will be called
940 // if and when the user resets all localStorage variables
943 resizeChartToHeight(height);
947 // string - the netdata server URL, without any path
948 this.host = self.data('host') || NETDATA.chartDefaults.host;
950 // make sure the host does not end with /
951 // all netdata API requests use absolute paths
952 while(this.host.slice(-1) === '/')
953 this.host = this.host.substring(0, this.host.length - 1);
955 // string - the grouping method requested by the user
956 this.method = self.data('method') || NETDATA.chartDefaults.method;
958 // the time-range requested by the user
959 this.after = self.data('after') || NETDATA.chartDefaults.after;
960 this.before = self.data('before') || NETDATA.chartDefaults.before;
962 // the pixels per point requested by the user
963 this.pixels_per_point = self.data('pixels-per-point') || 1;
964 this.points = self.data('points') || null;
966 // the dimensions requested by the user
967 this.dimensions = self.data('dimensions') || null;
969 // the chart library requested by the user
970 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
972 // object - the chart library used
977 this.colors_assigned = {};
978 this.colors_available = null;
980 // the element already created by the user
981 this.element_message = null;
983 // the element with the chart
984 this.element_chart = null;
986 // the element with the legend of the chart (if created by us)
987 this.element_legend = null;
988 this.element_legend_childs = {
998 this.chart_url = null; // string - the url to download chart info
999 this.chart = null; // object - the chart as downloaded from the server
1001 this.title = self.data('title') || null; // the title of the chart
1002 this.units = self.data('units') || null; // the units of the chart dimensions
1003 this.append_options = self.data('append-options') || null; // the units of the chart dimensions
1005 this.running = false; // boolean - true when the chart is being refreshed now
1006 this.validated = false; // boolean - has the chart been validated?
1007 this.enabled = true; // boolean - is the chart enabled for refresh?
1008 this.paused = false; // boolean - is the chart paused for any reason?
1009 this.selected = false; // boolean - is the chart shown a selection?
1010 this.debug = false; // boolean - console.log() debug info about this chart
1012 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1013 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1014 this.requested_after = null; // milliseconds - the timestamp of the request after param
1015 this.requested_before = null; // milliseconds - the timestamp of the request before param
1016 this.requested_padding = null;
1017 this.view_after = 0;
1018 this.view_before = 0;
1023 force_update_at: 0, // the timestamp to force the update at
1024 force_before_ms: null,
1025 force_after_ms: null
1030 force_update_at: 0, // the timestamp to force the update at
1031 force_before_ms: null,
1032 force_after_ms: null
1037 force_update_at: 0, // the timestamp to force the update at
1038 force_before_ms: null,
1039 force_after_ms: null
1042 // this is a pointer to one of the sub-classes below
1044 this.current = this.auto;
1046 // check the requested library is available
1047 // we don't initialize it here - it will be initialized when
1048 // this chart will be first used
1049 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1050 NETDATA.error(402, that.library_name);
1051 error('chart library "' + that.library_name + '" is not found');
1054 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1055 NETDATA.error(403, that.library_name);
1056 error('chart library "' + that.library_name + '" is not enabled');
1060 that.library = NETDATA.chartLibraries[that.library_name];
1062 // milliseconds - the time the last refresh took
1063 this.refresh_dt_ms = 0;
1065 // if we need to report the rendering speed
1066 // find the element that needs to be updated
1067 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1069 if(refresh_dt_element_name !== null)
1070 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1072 this.refresh_dt_element = null;
1074 this.dimensions_visibility = new dimensionsVisibility(this);
1076 this._updating = false;
1078 // ============================================================================================================
1079 // PRIVATE FUNCTIONS
1081 var createDOM = function() {
1082 if(that.enabled === false) return;
1084 if(that.element_message !== null) that.element_message.innerHTML = '';
1085 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1086 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1088 that.element.innerHTML = '';
1090 that.element_message = document.createElement('div');
1091 that.element_message.className = ' netdata-message hidden';
1092 that.element.appendChild(that.element_message);
1094 that.element_chart = document.createElement('div');
1095 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1096 that.element.appendChild(that.element_chart);
1098 if(that.hasLegend() === true) {
1099 that.element.className = "netdata-container-with-legend";
1100 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1102 that.element_legend = document.createElement('div');
1103 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1104 that.element.appendChild(that.element_legend);
1107 that.element.className = "netdata-container";
1108 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1110 that.element_legend = null;
1112 that.element_legend_childs.series = null;
1114 if(typeof(that.width) === 'string')
1115 $(that.element).css('width', that.width);
1116 else if(typeof(that.width) === 'number')
1117 $(that.element).css('width', that.width + 'px');
1119 if(typeof(that.library.aspect_ratio) === 'undefined') {
1120 if(typeof(that.height) === 'string')
1121 $(that.element).css('height', that.height);
1122 else if(typeof(that.height) === 'number')
1123 $(that.element).css('height', that.height + 'px');
1126 var w = that.element.offsetWidth;
1127 if(w === null || w === 0) {
1128 // the div is hidden
1129 // this will resize the chart when next viewed
1130 that.tm.last_resized = 0;
1133 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1136 if(NETDATA.chartDefaults.min_width !== null)
1137 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1139 that.tm.last_dom_created = new Date().getTime();
1145 * initialize state variables
1146 * destroy all (possibly) created state elements
1147 * create the basic DOM for a chart
1149 var init = function() {
1150 if(that.enabled === false) return;
1152 that.paused = false;
1153 that.selected = false;
1155 that.chart_created = false; // boolean - is the library.create() been called?
1156 that.updates_counter = 0; // numeric - the number of refreshes made so far
1157 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1158 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1161 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1162 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1163 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1165 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1166 last_updated: 0, // the timestamp the chart last updated with data
1167 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1169 // Used with NETDATA.globalPanAndZoom.seq
1170 last_visible_check: 0, // the time we last checked if it is visible
1171 last_resized: 0, // the time the chart was resized
1172 last_hidden: 0, // the time the chart was hidden
1173 last_unhidden: 0, // the time the chart was unhidden
1174 last_autorefreshed: 0 // the time the chart was last refreshed
1177 that.data = null; // the last data as downloaded from the netdata server
1178 that.data_url = 'invalid://'; // string - the last url used to update the chart
1179 that.data_points = 0; // number - the number of points returned from netdata
1180 that.data_after = 0; // milliseconds - the first timestamp of the data
1181 that.data_before = 0; // milliseconds - the last timestamp of the data
1182 that.data_update_every = 0; // milliseconds - the frequency to update the data
1184 that.tm.last_initialized = new Date().getTime();
1187 that.setMode('auto');
1190 var maxMessageFontSize = function() {
1191 // normally we want a font size, as tall as the element
1192 var h = that.element_message.clientHeight;
1194 // but give it some air, 20% let's say, or 5 pixels min
1195 var lost = Math.max(h * 0.2, 5);
1198 // center the text, vertically
1199 var paddingTop = (lost - 5) / 2;
1201 // but check the width too
1202 // it should fit 10 characters in it
1203 var w = that.element_message.clientWidth / 10;
1205 paddingTop += (h - w) / 2;
1209 // and don't make it too huge
1210 // 5% of the screen size is good
1211 if(h > screen.height / 20) {
1212 paddingTop += (h - (screen.height / 20)) / 2;
1213 h = screen.height / 20;
1217 that.element_message.style.fontSize = h.toString() + 'px';
1218 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1221 var showMessage = function(msg) {
1222 that.element_message.className = 'netdata-message';
1223 that.element_message.innerHTML = msg;
1224 that.element_message.style.fontSize = 'x-small';
1225 that.element_message.style.paddingTop = '0px';
1226 that.___messageHidden___ = undefined;
1229 var showMessageIcon = function(icon) {
1230 that.element_message.innerHTML = icon;
1231 that.element_message.className = 'netdata-message icon';
1232 maxMessageFontSize();
1233 that.___messageHidden___ = undefined;
1236 var hideMessage = function() {
1237 if(typeof that.___messageHidden___ === 'undefined') {
1238 that.___messageHidden___ = true;
1239 that.element_message.className = 'netdata-message hidden';
1243 var showRendering = function() {
1245 if(that.chart !== null) {
1246 if(that.chart.chart_type === 'line')
1247 icon = '<i class="fa fa-line-chart"></i>';
1249 icon = '<i class="fa fa-area-chart"></i>';
1252 icon = '<i class="fa fa-area-chart"></i>';
1254 showMessageIcon(icon + ' netdata');
1257 var showLoading = function() {
1258 if(that.chart_created === false) {
1259 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1265 var isHidden = function() {
1266 if(typeof that.___chartIsHidden___ !== 'undefined')
1272 // hide the chart, when it is not visible - called from isVisible()
1273 var hideChart = function() {
1274 // hide it, if it is not already hidden
1275 if(isHidden() === true) return;
1277 if(that.chart_created === true) {
1278 if(NETDATA.options.current.destroy_on_hide === true) {
1279 // we should destroy it
1284 that.element_chart.style.display = 'none';
1285 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1286 that.tm.last_hidden = new Date().getTime();
1289 // This works, but I not sure there are no corner cases somewhere
1290 // so it is commented - if the user has memory issues he can
1291 // set Destroy on Hide for all charts
1292 // that.data = null;
1296 that.___chartIsHidden___ = true;
1299 // unhide the chart, when it is visible - called from isVisible()
1300 var unhideChart = function() {
1301 if(isHidden() === false) return;
1303 that.___chartIsHidden___ = undefined;
1304 that.updates_since_last_unhide = 0;
1306 if(that.chart_created === false) {
1307 // we need to re-initialize it, to show our background
1308 // logo in bootstrap tabs, until the chart loads
1312 that.tm.last_unhidden = new Date().getTime();
1313 that.element_chart.style.display = '';
1314 if(that.element_legend !== null) that.element_legend.style.display = '';
1320 var canBeRendered = function() {
1321 if(isHidden() === true || that.isVisible(true) === false)
1327 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1328 var callChartLibraryUpdateSafely = function(data) {
1331 if(canBeRendered() === false)
1334 if(NETDATA.options.debug.chart_errors === true)
1335 status = that.library.update(that, data);
1338 status = that.library.update(that, data);
1345 if(status === false) {
1346 error('chart failed to be updated as ' + that.library_name);
1353 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1354 var callChartLibraryCreateSafely = function(data) {
1357 if(canBeRendered() === false)
1360 if(NETDATA.options.debug.chart_errors === true)
1361 status = that.library.create(that, data);
1364 status = that.library.create(that, data);
1371 if(status === false) {
1372 error('chart failed to be created as ' + that.library_name);
1376 that.chart_created = true;
1377 that.updates_since_last_creation = 0;
1381 // ----------------------------------------------------------------------------------------------------------------
1384 // resizeChart() - private
1385 // to be called just before the chart library to make sure that
1386 // a properly sized dom is available
1387 var resizeChart = function() {
1388 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1389 if(that.chart_created === false) return;
1391 if(that.needsRecreation()) {
1394 else if(typeof that.library.resize === 'function') {
1395 that.library.resize(that);
1397 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1398 $(that.element_legend_childs.nano).nanoScroller();
1400 maxMessageFontSize();
1403 that.tm.last_resized = new Date().getTime();
1407 // this is the actual chart resize algorithm
1409 // - resize the entire container
1410 // - update the internal states
1411 // - resize the chart as the div changes height
1412 // - update the scrollbar of the legend
1413 var resizeChartToHeight = function(h) {
1415 that.element.style.height = h;
1417 if(that.settings_id !== null)
1418 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1420 var now = new Date().getTime();
1421 NETDATA.options.last_page_scroll = now;
1422 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1425 that.tm.last_resized = 0;
1429 this.resizeHandler = function(e) {
1432 if(typeof this.event_resize === 'undefined'
1433 || this.event_resize.chart_original_w === 'undefined'
1434 || this.event_resize.chart_original_h === 'undefined')
1435 this.event_resize = {
1436 chart_original_w: this.element.clientWidth,
1437 chart_original_h: this.element.clientHeight,
1441 if(e.type === 'touchstart') {
1442 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1443 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1446 this.event_resize.mouse_start_x = e.clientX;
1447 this.event_resize.mouse_start_y = e.clientY;
1450 this.event_resize.chart_start_w = this.element.clientWidth;
1451 this.event_resize.chart_start_h = this.element.clientHeight;
1452 this.event_resize.chart_last_w = this.element.clientWidth;
1453 this.event_resize.chart_last_h = this.element.clientHeight;
1455 var now = new Date().getTime();
1456 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1457 // double click / double tap event
1459 // the optimal height of the chart
1460 // showing the entire legend
1461 var optimal = this.event_resize.chart_last_h
1462 + this.element_legend_childs.content.scrollHeight
1463 - this.element_legend_childs.content.clientHeight;
1465 // if we are not optimal, be optimal
1466 if(this.event_resize.chart_last_h != optimal)
1467 resizeChartToHeight(optimal.toString() + 'px');
1469 // else if we do not have the original height
1470 // reset to the original height
1471 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1472 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1475 this.event_resize.last = now;
1477 // process movement event
1478 document.onmousemove =
1479 document.ontouchmove =
1480 this.element_legend_childs.resize_handler.onmousemove =
1481 this.element_legend_childs.resize_handler.ontouchmove =
1486 case 'mousemove': y = e.clientY; break;
1487 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1491 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1493 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1494 resizeChartToHeight(newH.toString() + 'px');
1495 that.event_resize.chart_last_h = newH;
1500 // process end event
1501 document.onmouseup =
1502 document.ontouchend =
1503 this.element_legend_childs.resize_handler.onmouseup =
1504 this.element_legend_childs.resize_handler.ontouchend =
1506 // remove all the hooks
1507 document.onmouseup =
1508 document.onmousemove =
1509 document.ontouchmove =
1510 document.ontouchend =
1511 that.element_legend_childs.resize_handler.onmousemove =
1512 that.element_legend_childs.resize_handler.ontouchmove =
1513 that.element_legend_childs.resize_handler.onmouseout =
1514 that.element_legend_childs.resize_handler.onmouseup =
1515 that.element_legend_childs.resize_handler.ontouchend =
1518 // allow auto-refreshes
1519 NETDATA.options.auto_refresher_stop_until = 0;
1525 var noDataToShow = function() {
1526 showMessageIcon('<i class="fa fa-warning"></i> empty');
1527 that.legendUpdateDOM();
1528 that.tm.last_autorefreshed = new Date().getTime();
1529 // that.data_update_every = 30 * 1000;
1530 //that.element_chart.style.display = 'none';
1531 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1532 //that.___chartIsHidden___ = true;
1535 // ============================================================================================================
1538 this.error = function(msg) {
1542 this.setMode = function(m) {
1543 if(this.current !== null && this.current.name === m) return;
1546 this.current = this.auto;
1547 else if(m === 'pan')
1548 this.current = this.pan;
1549 else if(m === 'zoom')
1550 this.current = this.zoom;
1552 this.current = this.auto;
1554 this.current.force_update_at = 0;
1555 this.current.force_before_ms = null;
1556 this.current.force_after_ms = null;
1558 this.tm.last_mode_switch = new Date().getTime();
1561 // ----------------------------------------------------------------------------------------------------------------
1562 // global selection sync
1564 // prevent to global selection sync for some time
1565 this.globalSelectionSyncDelay = function(ms) {
1566 if(NETDATA.options.current.sync_selection === false)
1569 if(typeof ms === 'number')
1570 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
1572 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
1575 // can we globally apply selection sync?
1576 this.globalSelectionSyncAbility = function() {
1577 if(NETDATA.options.current.sync_selection === false)
1580 if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
1586 this.globalSelectionSyncIsMaster = function() {
1587 if(NETDATA.globalSelectionSync.state === this)
1593 // this chart is the master of the global selection sync
1594 this.globalSelectionSyncBeMaster = function() {
1596 if(this.globalSelectionSyncIsMaster()) {
1597 if(this.debug === true)
1598 this.log('sync: I am the master already.');
1603 if(NETDATA.globalSelectionSync.state) {
1604 if(this.debug === true)
1605 this.log('sync: I am not the sync master. Resetting global sync.');
1607 this.globalSelectionSyncStop();
1610 // become the master
1611 if(this.debug === true)
1612 this.log('sync: becoming sync master.');
1614 this.selected = true;
1615 NETDATA.globalSelectionSync.state = this;
1617 // find the all slaves
1618 var targets = NETDATA.options.targets;
1619 var len = targets.length;
1624 if(this.debug === true)
1625 st.log('sync: not adding me to sync');
1627 else if(st.globalSelectionSyncIsEligible()) {
1628 if(this.debug === true)
1629 st.log('sync: adding to sync as slave');
1631 st.globalSelectionSyncBeSlave();
1635 // this.globalSelectionSyncDelay(100);
1638 // can the chart participate to the global selection sync as a slave?
1639 this.globalSelectionSyncIsEligible = function() {
1640 if(this.enabled === true
1641 && this.library !== null
1642 && typeof this.library.setSelection === 'function'
1643 && this.isVisible() === true
1644 && this.chart_created === true)
1650 // this chart becomes a slave of the global selection sync
1651 this.globalSelectionSyncBeSlave = function() {
1652 if(NETDATA.globalSelectionSync.state !== this)
1653 NETDATA.globalSelectionSync.slaves.push(this);
1656 // sync all the visible charts to the given time
1657 // this is to be called from the chart libraries
1658 this.globalSelectionSync = function(t) {
1659 if(this.globalSelectionSyncAbility() === false) {
1660 if(this.debug === true)
1661 this.log('sync: cannot sync (yet?).');
1666 if(this.globalSelectionSyncIsMaster() === false) {
1667 if(this.debug === true)
1668 this.log('sync: trying to be sync master.');
1670 this.globalSelectionSyncBeMaster();
1672 if(this.globalSelectionSyncAbility() === false) {
1673 if(this.debug === true)
1674 this.log('sync: cannot sync (yet?).');
1680 NETDATA.globalSelectionSync.last_t = t;
1681 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1686 // stop syncing all charts to the given time
1687 this.globalSelectionSyncStop = function() {
1688 if(NETDATA.globalSelectionSync.slaves.length) {
1689 if(this.debug === true)
1690 this.log('sync: cleaning up...');
1692 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1694 if(that.debug === true)
1695 st.log('sync: not adding me to sync stop');
1698 if(that.debug === true)
1699 st.log('sync: removed slave from sync');
1701 st.clearSelection();
1705 NETDATA.globalSelectionSync.last_t = 0;
1706 NETDATA.globalSelectionSync.slaves = [];
1707 NETDATA.globalSelectionSync.state = null;
1710 this.clearSelection();
1713 this.setSelection = function(t) {
1714 if(typeof this.library.setSelection === 'function') {
1715 if(this.library.setSelection(this, t) === true)
1716 this.selected = true;
1718 this.selected = false;
1720 else this.selected = true;
1722 if(this.selected === true && this.debug === true)
1723 this.log('selection set to ' + t.toString());
1725 return this.selected;
1728 this.clearSelection = function() {
1729 if(this.selected === true) {
1730 if(typeof this.library.clearSelection === 'function') {
1731 if(this.library.clearSelection(this) === true)
1732 this.selected = false;
1734 this.selected = true;
1736 else this.selected = false;
1738 if(this.selected === false && this.debug === true)
1739 this.log('selection cleared');
1744 return this.selected;
1747 // find if a timestamp (ms) is shown in the current chart
1748 this.timeIsVisible = function(t) {
1749 if(t >= this.data_after && t <= this.data_before)
1754 this.calculateRowForTime = function(t) {
1755 if(this.timeIsVisible(t) === false) return -1;
1756 return Math.floor((t - this.data_after) / this.data_update_every);
1759 // ----------------------------------------------------------------------------------------------------------------
1762 this.log = function(msg) {
1763 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
1766 this.pauseChart = function() {
1767 if(this.paused === false) {
1768 if(this.debug === true)
1769 this.log('pauseChart()');
1775 this.unpauseChart = function() {
1776 if(this.paused === true) {
1777 if(this.debug === true)
1778 this.log('unpauseChart()');
1780 this.paused = false;
1784 this.resetChart = function(dont_clear_master, dont_update) {
1785 if(this.debug === true)
1786 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
1788 if(typeof dont_clear_master === 'undefined')
1789 dont_clear_master = false;
1791 if(typeof dont_update === 'undefined')
1792 dont_update = false;
1794 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
1795 if(this.debug === true)
1796 this.log('resetChart() diverting to clearMaster().');
1797 // this will call us back with master === true
1798 NETDATA.globalPanAndZoom.clearMaster();
1802 this.clearSelection();
1804 this.tm.pan_and_zoom_seq = 0;
1806 this.setMode('auto');
1807 this.current.force_update_at = 0;
1808 this.current.force_before_ms = null;
1809 this.current.force_after_ms = null;
1810 this.tm.last_autorefreshed = 0;
1811 this.paused = false;
1812 this.selected = false;
1813 this.enabled = true;
1814 // this.debug = false;
1816 // do not update the chart here
1817 // or the chart will flip-flop when it is the master
1818 // of a selection sync and another chart becomes
1821 if(dont_update !== true && this.isVisible() === true) {
1826 this.updateChartPanOrZoom = function(after, before) {
1827 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
1830 if(this.debug === true)
1833 if(before < after) {
1834 if(this.debug === true)
1835 this.log(logme + 'flipped parameters, rejecting it.');
1840 if(typeof this.fixed_min_duration === 'undefined')
1841 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1843 var min_duration = this.fixed_min_duration;
1844 var current_duration = Math.round(this.view_before - this.view_after);
1846 // round the numbers
1847 after = Math.round(after);
1848 before = Math.round(before);
1850 // align them to update_every
1851 // stretching them further away
1852 after -= after % this.data_update_every;
1853 before += this.data_update_every - (before % this.data_update_every);
1855 // the final wanted duration
1856 var wanted_duration = before - after;
1858 // to allow panning, accept just a point below our minimum
1859 if((current_duration - this.data_update_every) < min_duration)
1860 min_duration = current_duration - this.data_update_every;
1862 // we do it, but we adjust to minimum size and return false
1863 // when the wanted size is below the current and the minimum
1865 if(wanted_duration < current_duration && wanted_duration < min_duration) {
1866 if(this.debug === true)
1867 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1869 min_duration = this.fixed_min_duration;
1871 var dt = (min_duration - wanted_duration) / 2;
1874 wanted_duration = before - after;
1878 var tolerance = this.data_update_every * 2;
1879 var movement = Math.abs(before - this.view_before);
1881 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
1882 if(this.debug === true)
1883 this.log(logme + 'REJECTING UPDATE: current/min duration: ' + (current_duration / 1000).toString() + '/' + (this.fixed_min_duration / 1000).toString() + ', wanted duration: ' + (wanted_duration / 1000).toString() + ', duration diff: ' + (Math.round(Math.abs(current_duration - wanted_duration) / 1000)).toString() + ', movement: ' + (movement / 1000).toString() + ', tolerance: ' + (tolerance / 1000).toString() + ', returning: ' + false);
1887 if(this.current.name === 'auto') {
1888 this.log(logme + 'caller called me with mode: ' + this.current.name);
1889 this.setMode('pan');
1892 if(this.debug === true)
1893 this.log(logme + 'ACCEPTING UPDATE: current/min duration: ' + (current_duration / 1000).toString() + '/' + (this.fixed_min_duration / 1000).toString() + ', wanted duration: ' + (wanted_duration / 1000).toString() + ', duration diff: ' + (Math.round(Math.abs(current_duration - wanted_duration) / 1000)).toString() + ', movement: ' + (movement / 1000).toString() + ', tolerance: ' + (tolerance / 1000).toString() + ', returning: ' + ret);
1895 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1896 this.current.force_after_ms = after;
1897 this.current.force_before_ms = before;
1898 NETDATA.globalPanAndZoom.setMaster(this, after, before);
1902 this.legendFormatValue = function(value) {
1903 if(value === null || value === 'undefined') return '-';
1904 if(typeof value !== 'number') return value;
1906 var abs = Math.abs(value);
1907 if(abs >= 1000) return (Math.round(value)).toLocaleString();
1908 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1909 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
1910 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1911 return (Math.round(value * 10000) / 10000).toLocaleString();
1914 this.legendSetLabelValue = function(label, value) {
1915 var series = this.element_legend_childs.series[label];
1916 if(typeof series === 'undefined') return;
1917 if(series.value === null && series.user === null) return;
1919 // if the value has not changed, skip DOM update
1920 //if(series.last === value) return;
1923 if(typeof value === 'number') {
1924 var v = Math.abs(value);
1925 s = r = this.legendFormatValue(value);
1927 if(typeof series.last === 'number') {
1928 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1929 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1930 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1932 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1937 series.last = value;
1940 if(series.value !== null) series.value.innerHTML = s;
1941 if(series.user !== null) series.user.innerHTML = r;
1944 this.legendSetDate = function(ms) {
1945 if(typeof ms !== 'number') {
1946 this.legendShowUndefined();
1950 var d = new Date(ms);
1952 if(this.element_legend_childs.title_date)
1953 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
1955 if(this.element_legend_childs.title_time)
1956 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
1958 if(this.element_legend_childs.title_units)
1959 this.element_legend_childs.title_units.innerHTML = this.units;
1962 this.legendShowUndefined = function() {
1963 if(this.element_legend_childs.title_date)
1964 this.element_legend_childs.title_date.innerHTML = ' ';
1966 if(this.element_legend_childs.title_time)
1967 this.element_legend_childs.title_time.innerHTML = this.chart.name;
1969 if(this.element_legend_childs.title_units)
1970 this.element_legend_childs.title_units.innerHTML = ' ';
1972 if(this.data && this.element_legend_childs.series !== null) {
1973 var labels = this.data.dimension_names;
1974 var i = labels.length;
1976 var label = labels[i];
1978 if(typeof label === 'undefined') continue;
1979 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1980 this.legendSetLabelValue(label, null);
1985 this.legendShowLatestValues = function() {
1986 if(this.chart === null) return;
1987 if(this.selected) return;
1989 if(this.data === null || this.element_legend_childs.series === null) {
1990 this.legendShowUndefined();
1994 var show_undefined = true;
1995 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
1996 show_undefined = false;
1998 if(show_undefined) {
1999 this.legendShowUndefined();
2003 this.legendSetDate(this.view_before);
2005 var labels = this.data.dimension_names;
2006 var i = labels.length;
2008 var label = labels[i];
2010 if(typeof label === 'undefined') continue;
2011 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2014 this.legendSetLabelValue(label, null);
2016 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2020 this.legendReset = function() {
2021 this.legendShowLatestValues();
2024 // this should be called just ONCE per dimension per chart
2025 this._chartDimensionColor = function(label) {
2026 if(this.colors === null) this.chartColors();
2028 if(typeof this.colors_assigned[label] === 'undefined') {
2029 if(this.colors_available.length === 0) {
2030 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2031 this.colors_available.push(NETDATA.themes.current.colors[i]);
2034 this.colors_assigned[label] = this.colors_available.shift();
2036 if(this.debug === true)
2037 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2040 if(this.debug === true)
2041 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2044 this.colors.push(this.colors_assigned[label]);
2045 return this.colors_assigned[label];
2048 this.chartColors = function() {
2049 if(this.colors !== null) return this.colors;
2051 this.colors = new Array();
2052 this.colors_available = new Array();
2055 var c = $(this.element).data('colors');
2056 // this.log('read colors: ' + c);
2057 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2058 if(typeof c !== 'string') {
2059 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2066 for(i = 0, len = c.length; i < len ; i++) {
2068 this.colors_available.push(c[i]);
2069 // this.log('adding color: ' + c[i]);
2075 // push all the standard colors too
2076 for(i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2077 this.colors_available.push(NETDATA.themes.current.colors[i]);
2082 this.legendUpdateDOM = function() {
2085 // check that the legend DOM is up to date for the downloaded dimensions
2086 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2087 // this.log('the legend does not have any series - requesting legend update');
2090 else if(this.data === null) {
2091 // this.log('the chart does not have any data - requesting legend update');
2094 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2098 var labels = this.data.dimension_names.toString();
2099 if(labels !== this.element_legend_childs.series.labels_key) {
2102 if(this.debug === true)
2103 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2107 if(needed === false) {
2108 // make sure colors available
2111 // do we have to update the current values?
2112 // we do this, only when the visible chart is current
2113 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2114 if(this.debug === true)
2115 this.log('chart is in latest position... updating values on legend...');
2117 //var labels = this.data.dimension_names;
2118 //var i = labels.length;
2120 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2124 if(this.colors === null) {
2125 // this is the first time we update the chart
2126 // let's assign colors to all dimensions
2127 if(this.library.track_colors() === true)
2128 for(var dim in this.chart.dimensions)
2129 this._chartDimensionColor(this.chart.dimensions[dim].name);
2131 // we will re-generate the colors for the chart
2132 // based on the selected dimensions
2135 if(this.debug === true)
2136 this.log('updating Legend DOM');
2138 // mark all dimensions as invalid
2139 this.dimensions_visibility.invalidateAll();
2141 var genLabel = function(state, parent, name, count) {
2142 var color = state._chartDimensionColor(name);
2144 var user_element = null;
2145 var user_id = self.data('show-value-of-' + name + '-at') || null;
2146 if(user_id !== null) {
2147 user_element = document.getElementById(user_id) || null;
2148 if(user_element === null)
2149 state.log('Cannot find element with id: ' + user_id);
2152 state.element_legend_childs.series[name] = {
2153 name: document.createElement('span'),
2154 value: document.createElement('span'),
2159 var label = state.element_legend_childs.series[name];
2161 // create the dimension visibility tracking for this label
2162 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2164 var rgb = NETDATA.colorHex2Rgb(color);
2165 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2166 + state.chart.chart_type
2167 + '" style="background-color: '
2168 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2169 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2171 var text = document.createTextNode(' ' + name);
2172 label.name.appendChild(text);
2175 parent.appendChild(document.createElement('br'));
2177 parent.appendChild(label.name);
2178 parent.appendChild(label.value);
2181 var content = document.createElement('div');
2183 if(this.hasLegend()) {
2184 this.element_legend_childs = {
2186 resize_handler: document.createElement('div'),
2187 toolbox: document.createElement('div'),
2188 toolbox_left: document.createElement('div'),
2189 toolbox_right: document.createElement('div'),
2190 toolbox_reset: document.createElement('div'),
2191 toolbox_zoomin: document.createElement('div'),
2192 toolbox_zoomout: document.createElement('div'),
2193 toolbox_volume: document.createElement('div'),
2194 title_date: document.createElement('span'),
2195 title_time: document.createElement('span'),
2196 title_units: document.createElement('span'),
2197 nano: document.createElement('div'),
2199 paneClass: 'netdata-legend-series-pane',
2200 sliderClass: 'netdata-legend-series-slider',
2201 contentClass: 'netdata-legend-series-content',
2202 enabledClass: '__enabled',
2203 flashedClass: '__flashed',
2204 activeClass: '__active',
2206 alwaysVisible: true,
2212 this.element_legend.innerHTML = '';
2214 if(this.library.toolboxPanAndZoom !== null) {
2216 function get_pan_and_zoom_step(event) {
2218 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2220 else if (event.shiftKey)
2221 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2223 else if (event.altKey)
2224 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2227 return NETDATA.options.current.pan_and_zoom_factor;
2230 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2231 this.element.appendChild(this.element_legend_childs.toolbox);
2233 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2234 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2235 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2236 this.element_legend_childs.toolbox_left.onclick = function(e) {
2239 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2240 var before = that.view_before - step;
2241 var after = that.view_after - step;
2242 if(after >= that.netdata_first)
2243 that.library.toolboxPanAndZoom(that, after, before);
2245 if(NETDATA.options.current.show_help === true)
2246 $(this.element_legend_childs.toolbox_left).popover({
2251 placement: 'bottom',
2252 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2254 content: 'Pan the chart to the left. You can also <b>drag it</b> with your mouse or your finger (on touch devices).<br/><small>Help, can be disabled from the settings.</small>'
2258 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2259 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2260 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2261 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2263 NETDATA.resetAllCharts(that);
2265 if(NETDATA.options.current.show_help === true)
2266 $(this.element_legend_childs.toolbox_reset).popover({
2271 placement: 'bottom',
2272 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2273 title: 'Chart Reset',
2274 content: 'Reset all the charts to their default auto-refreshing state. You can also <b>double click</b> the chart contents with your mouse or your finger (on touch devices).<br/><small>Help, can be disabled from the settings.</small>'
2277 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2278 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2279 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2280 this.element_legend_childs.toolbox_right.onclick = function(e) {
2282 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2283 var before = that.view_before + step;
2284 var after = that.view_after + step;
2285 if(before <= that.netdata_last)
2286 that.library.toolboxPanAndZoom(that, after, before);
2288 if(NETDATA.options.current.show_help === true)
2289 $(this.element_legend_childs.toolbox_right).popover({
2294 placement: 'bottom',
2295 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2297 content: 'Pan the chart to the right. You can also <b>drag it</b> with your mouse or your finger (on touch devices).<br/><small>Help, can be disabled from the settings.</small>'
2301 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2302 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2303 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2304 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2306 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2307 var before = that.view_before - dt;
2308 var after = that.view_after + dt;
2309 that.library.toolboxPanAndZoom(that, after, before);
2311 if(NETDATA.options.current.show_help === true)
2312 $(this.element_legend_childs.toolbox_zoomin).popover({
2317 placement: 'bottom',
2318 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2319 title: 'Chart Zoom In',
2320 content: 'Zoom in the chart. You can also press SHIFT and select an area of the chart to zoom in. On Chrome and Opera, you can press the SHIFT or the ALT keys and then use the mouse wheel to zoom in or out.<br/><small>Help, can be disabled from the settings.</small>'
2323 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2324 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2325 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2326 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2328 var dt = (((that.view_before - that.view_after) / (1.0 - (get_pan_and_zoom_step(e) * 0.8)) - (that.view_before - that.view_after)) / 2);
2329 var before = that.view_before + dt;
2330 var after = that.view_after - dt;
2332 that.library.toolboxPanAndZoom(that, after, before);
2334 if(NETDATA.options.current.show_help === true)
2335 $(this.element_legend_childs.toolbox_zoomout).popover({
2340 placement: 'bottom',
2341 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2342 title: 'Chart Zoom Out',
2343 content: 'Zoom out the chart. On Chrome and Opera, you can also press the SHIFT or the ALT keys and then use the mouse wheel to zoom in or out.<br/><small>Help, can be disabled from the settings.</small>'
2346 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2347 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2348 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2349 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2350 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2351 //e.preventDefault();
2352 //alert('clicked toolbox_volume on ' + that.id);
2356 this.element_legend_childs.toolbox = null;
2357 this.element_legend_childs.toolbox_left = null;
2358 this.element_legend_childs.toolbox_reset = null;
2359 this.element_legend_childs.toolbox_right = null;
2360 this.element_legend_childs.toolbox_zoomin = null;
2361 this.element_legend_childs.toolbox_zoomout = null;
2362 this.element_legend_childs.toolbox_volume = null;
2365 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2366 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2367 this.element.appendChild(this.element_legend_childs.resize_handler);
2368 if(NETDATA.options.current.show_help === true)
2369 $(this.element_legend_childs.resize_handler).popover({
2374 placement: 'bottom',
2375 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2376 title: 'Chart Resize',
2377 content: 'Drag this point with your mouse or your finger (on touch devices), to resize the chart vertically. You can also <b>double click it</b> or <b>double tap it</b> to reset between 2 states: the default and the one that fits all the values.<br/><small>Help, can be disabled from the settings.</small>'
2381 this.element_legend_childs.resize_handler.onmousedown =
2383 that.resizeHandler(e);
2387 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2388 that.resizeHandler(e);
2391 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2392 this.element_legend.appendChild(this.element_legend_childs.title_date);
2394 this.element_legend.appendChild(document.createElement('br'));
2396 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2397 this.element_legend.appendChild(this.element_legend_childs.title_time);
2399 this.element_legend.appendChild(document.createElement('br'));
2401 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2402 this.element_legend.appendChild(this.element_legend_childs.title_units);
2404 this.element_legend.appendChild(document.createElement('br'));
2406 this.element_legend_childs.nano.className = 'netdata-legend-series';
2407 this.element_legend.appendChild(this.element_legend_childs.nano);
2409 content.className = 'netdata-legend-series-content';
2410 this.element_legend_childs.nano.appendChild(content);
2412 if(NETDATA.options.current.show_help === true)
2413 $(content).popover({
2418 placement: 'bottom',
2419 title: 'Chart Legend',
2420 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2421 content: 'You can click or tap on the values or the labels to select dimentions. By pressing SHIFT or CONTROL, you can enable or disable multiple dimensions.<br/><small>Help, can be disabled from the settings.</small>'
2425 this.element_legend_childs = {
2427 resize_handler: null,
2430 toolbox_right: null,
2431 toolbox_reset: null,
2432 toolbox_zoomin: null,
2433 toolbox_zoomout: null,
2434 toolbox_volume: null,
2445 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2446 if(this.debug === true)
2447 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2449 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2450 genLabel(this, content, this.data.dimension_names[i], i);
2454 var tmp = new Array();
2455 for(var dim in this.chart.dimensions) {
2456 tmp.push(this.chart.dimensions[dim].name);
2457 genLabel(this, content, this.chart.dimensions[dim].name, i);
2459 this.element_legend_childs.series.labels_key = tmp.toString();
2460 if(this.debug === true)
2461 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2464 // create a hidden div to be used for hidding
2465 // the original legend of the chart library
2466 var el = document.createElement('div');
2467 if(this.element_legend !== null)
2468 this.element_legend.appendChild(el);
2469 el.style.display = 'none';
2471 this.element_legend_childs.hidden = document.createElement('div');
2472 el.appendChild(this.element_legend_childs.hidden);
2474 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2475 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2477 this.legendShowLatestValues();
2480 this.hasLegend = function() {
2481 if(typeof this.___hasLegendCache___ !== 'undefined')
2482 return this.___hasLegendCache___;
2485 if(this.library && this.library.legend(this) === 'right-side') {
2486 var legend = $(this.element).data('legend') || 'yes';
2487 if(legend === 'yes') leg = true;
2490 this.___hasLegendCache___ = leg;
2494 this.legendWidth = function() {
2495 return (this.hasLegend())?140:0;
2498 this.legendHeight = function() {
2499 return $(this.element).height();
2502 this.chartWidth = function() {
2503 return $(this.element).width() - this.legendWidth();
2506 this.chartHeight = function() {
2507 return $(this.element).height();
2510 this.chartPixelsPerPoint = function() {
2511 // force an options provided detail
2512 var px = this.pixels_per_point;
2514 if(this.library && px < this.library.pixels_per_point(this))
2515 px = this.library.pixels_per_point(this);
2517 if(px < NETDATA.options.current.pixels_per_point)
2518 px = NETDATA.options.current.pixels_per_point;
2523 this.needsRecreation = function() {
2525 this.chart_created === true
2527 && this.library.autoresize() === false
2528 && this.tm.last_resized < NETDATA.options.last_resized
2532 this.chartURL = function() {
2533 var after, before, points_multiplier = 1;
2534 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2535 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2537 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2538 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2539 this.view_after = after * 1000;
2540 this.view_before = before * 1000;
2542 this.requested_padding = null;
2543 points_multiplier = 1;
2545 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2546 this.tm.pan_and_zoom_seq = 0;
2548 before = Math.round(this.current.force_before_ms / 1000);
2549 after = Math.round(this.current.force_after_ms / 1000);
2550 this.view_after = after * 1000;
2551 this.view_before = before * 1000;
2553 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2554 this.requested_padding = Math.round((before - after) / 2);
2555 after -= this.requested_padding;
2556 before += this.requested_padding;
2557 this.requested_padding *= 1000;
2558 points_multiplier = 2;
2561 this.current.force_before_ms = null;
2562 this.current.force_after_ms = null;
2565 this.tm.pan_and_zoom_seq = 0;
2567 before = this.before;
2569 this.view_after = after * 1000;
2570 this.view_before = before * 1000;
2572 this.requested_padding = null;
2573 points_multiplier = 1;
2576 this.requested_after = after * 1000;
2577 this.requested_before = before * 1000;
2579 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2581 // build the data URL
2582 this.data_url = this.host + this.chart.data_url;
2583 this.data_url += "&format=" + this.library.format();
2584 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2585 this.data_url += "&group=" + this.method;
2586 this.data_url += "&options=" + this.library.options(this);
2587 this.data_url += '|jsonwrap';
2589 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2590 this.data_url += '|nonzero';
2592 if(this.append_options !== null)
2593 this.data_url += '|' + this.append_options.toString();
2596 this.data_url += "&after=" + after.toString();
2599 this.data_url += "&before=" + before.toString();
2602 this.data_url += "&dimensions=" + this.dimensions;
2604 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2605 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2608 this.redrawChart = function() {
2609 if(this.data !== null)
2610 this.updateChartWithData(this.data);
2613 this.updateChartWithData = function(data) {
2614 if(this.debug === true)
2615 this.log('updateChartWithData() called.');
2617 // this may force the chart to be re-created
2621 this.updates_counter++;
2622 this.updates_since_last_unhide++;
2623 this.updates_since_last_creation++;
2625 var started = new Date().getTime();
2627 // if the result is JSON, find the latest update-every
2628 this.data_update_every = data.view_update_every * 1000;
2629 this.data_after = data.after * 1000;
2630 this.data_before = data.before * 1000;
2631 this.netdata_first = data.first_entry * 1000;
2632 this.netdata_last = data.last_entry * 1000;
2633 this.data_points = data.points;
2636 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2637 if(this.view_after < this.data_after) {
2638 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2639 this.view_after = this.data_after;
2642 if(this.view_before > this.data_before) {
2643 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2644 this.view_before = this.data_before;
2648 this.view_after = this.data_after;
2649 this.view_before = this.data_before;
2652 if(this.debug === true) {
2653 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2655 if(this.current.force_after_ms)
2656 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2658 this.log('STATUS: forced : unset');
2660 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2661 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2662 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2663 this.log('STATUS: points : ' + (this.data_points).toString());
2666 if(this.data_points === 0) {
2671 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2672 if(this.debug === true)
2673 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2675 this.chart_created = false;
2678 // check and update the legend
2679 this.legendUpdateDOM();
2681 if(this.chart_created === true
2682 && typeof this.library.update === 'function') {
2684 if(this.debug === true)
2685 this.log('updating chart...');
2687 if(callChartLibraryUpdateSafely(data) === false)
2691 if(this.debug === true)
2692 this.log('creating chart...');
2694 if(callChartLibraryCreateSafely(data) === false)
2698 this.legendShowLatestValues();
2699 if(this.selected === true)
2700 NETDATA.globalSelectionSync.stop();
2702 // update the performance counters
2703 var now = new Date().getTime();
2704 this.tm.last_updated = now;
2706 // don't update last_autorefreshed if this chart is
2707 // forced to be updated with global PanAndZoom
2708 if(NETDATA.globalPanAndZoom.isActive())
2709 this.tm.last_autorefreshed = 0;
2711 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
2712 this.tm.last_autorefreshed = now - (now % this.data_update_every);
2714 this.tm.last_autorefreshed = now;
2717 this.refresh_dt_ms = now - started;
2718 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2720 if(this.refresh_dt_element !== null)
2721 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2724 this.updateChart = function(callback) {
2725 if(this.debug === true)
2726 this.log('updateChart() called.');
2728 if(this._updating === true) {
2729 if(this.debug === true)
2730 this.log('I am already updating...');
2732 if(typeof callback === 'function') callback();
2736 // due to late initialization of charts and libraries
2737 // we need to check this too
2738 if(this.enabled === false) {
2739 if(this.debug === true)
2740 this.log('I am not enabled');
2742 if(typeof callback === 'function') callback();
2746 if(canBeRendered() === false) {
2747 if(typeof callback === 'function') callback();
2751 if(this.chart === null) {
2752 this.getChart(function() { that.updateChart(callback); });
2756 if(this.library.initialized === false) {
2757 if(this.library.enabled === true) {
2758 this.library.initialize(function() { that.updateChart(callback); });
2762 error('chart library "' + this.library_name + '" is not available.');
2763 if(typeof callback === 'function') callback();
2768 this.clearSelection();
2771 if(this.debug === true)
2772 this.log('updating from ' + this.data_url);
2774 NETDATA.statistics.refreshes_total++;
2775 NETDATA.statistics.refreshes_active++;
2777 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
2778 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
2780 this._updating = true;
2782 this.xhr = $.ajax( {
2787 .success(function(data) {
2788 if(that.debug === true)
2789 that.log('data received. updating chart.');
2791 that.updateChartWithData(data);
2794 error('data download failed for url: ' + that.data_url);
2796 .always(function() {
2797 NETDATA.statistics.refreshes_active--;
2798 that._updating = false;
2799 if(typeof callback === 'function') callback();
2805 this.isVisible = function(nocache) {
2806 if(typeof nocache === 'undefined')
2809 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2811 // caching - we do not evaluate the charts visibility
2812 // if the page has not been scrolled since the last check
2813 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2814 return this.___isVisible___;
2816 this.tm.last_visible_check = new Date().getTime();
2818 var wh = window.innerHeight;
2819 var x = this.element.getBoundingClientRect();
2823 if(x.width === 0 || x.height === 0) {
2825 this.___isVisible___ = false;
2826 return this.___isVisible___;
2829 if(x.top < 0 && -x.top > x.height) {
2830 // the chart is entirely above
2831 ret = -x.top - x.height;
2833 else if(x.top > wh) {
2834 // the chart is entirely below
2838 if(ret > tolerance) {
2839 // the chart is too far
2842 this.___isVisible___ = false;
2843 return this.___isVisible___;
2846 // the chart is inside or very close
2849 this.___isVisible___ = true;
2850 return this.___isVisible___;
2854 this.isAutoRefreshable = function() {
2855 return (this.current.autorefresh);
2858 this.canBeAutoRefreshed = function() {
2859 var now = new Date().getTime();
2861 if(this.running === true) {
2862 if(this.debug === true)
2863 this.log('I am already running');
2868 if(this.enabled === false) {
2869 if(this.debug === true)
2870 this.log('I am not enabled');
2875 if(this.library === null || this.library.enabled === false) {
2876 error('charting library "' + this.library_name + '" is not available');
2877 if(this.debug === true)
2878 this.log('My chart library ' + this.library_name + ' is not available');
2883 if(this.isVisible() === false) {
2884 if(NETDATA.options.debug.visibility === true || this.debug === true)
2885 this.log('I am not visible');
2890 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2891 if(this.debug === true)
2892 this.log('timed force update detected - allowing this update');
2894 this.current.force_update_at = 0;
2898 if(this.isAutoRefreshable() === true) {
2899 // allow the first update, even if the page is not visible
2900 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2901 if(NETDATA.options.debug.focus === true || this.debug === true)
2902 this.log('canBeAutoRefreshed(): page does not have focus');
2907 if(this.needsRecreation() === true) {
2908 if(this.debug === true)
2909 this.log('canBeAutoRefreshed(): needs re-creation.');
2914 // options valid only for autoRefresh()
2915 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
2916 if(NETDATA.globalPanAndZoom.isActive()) {
2917 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
2918 if(this.debug === true)
2919 this.log('canBeAutoRefreshed(): global panning: I need an update.');
2924 if(this.debug === true)
2925 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
2931 if(this.selected === true) {
2932 if(this.debug === true)
2933 this.log('canBeAutoRefreshed(): I have a selection in place.');
2938 if(this.paused === true) {
2939 if(this.debug === true)
2940 this.log('canBeAutoRefreshed(): I am paused.');
2945 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
2946 if(this.debug === true)
2947 this.log('canBeAutoRefreshed(): It is time to update me.');
2957 this.autoRefresh = function(callback) {
2958 if(this.canBeAutoRefreshed() === true && this.running === false) {
2961 state.running = true;
2962 state.updateChart(function() {
2963 state.running = false;
2965 if(typeof callback !== 'undefined')
2970 if(typeof callback !== 'undefined')
2975 this._defaultsFromDownloadedChart = function(chart) {
2977 this.chart_url = chart.url;
2978 this.data_update_every = chart.update_every * 1000;
2979 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2980 this.tm.last_info_downloaded = new Date().getTime();
2982 if(this.title === null)
2983 this.title = chart.title;
2985 if(this.units === null)
2986 this.units = chart.units;
2989 // fetch the chart description from the netdata server
2990 this.getChart = function(callback) {
2991 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
2993 this._defaultsFromDownloadedChart(this.chart);
2994 if(typeof callback === 'function') callback();
2997 this.chart_url = "/api/v1/chart?chart=" + this.id;
2999 if(this.debug === true)
3000 this.log('downloading ' + this.chart_url);
3003 url: this.host + this.chart_url,
3007 .done(function(chart) {
3008 chart.url = that.chart_url;
3009 that._defaultsFromDownloadedChart(chart);
3010 NETDATA.chartRegistry.add(that.host, that.id, chart);
3013 NETDATA.error(404, that.chart_url);
3014 error('chart not found on url "' + that.chart_url + '"');
3016 .always(function() {
3017 if(typeof callback === 'function') callback();
3022 // ============================================================================================================
3028 NETDATA.resetAllCharts = function(state) {
3029 // first clear the global selection sync
3030 // to make sure no chart is in selected state
3031 state.globalSelectionSyncStop();
3033 // there are 2 possibilities here
3034 // a. state is the global Pan and Zoom master
3035 // b. state is not the global Pan and Zoom master
3037 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3040 // clear the global Pan and Zoom
3041 // this will also refresh the master
3042 // and unblock any charts currently mirroring the master
3043 NETDATA.globalPanAndZoom.clearMaster();
3045 // if we were not the master, reset our status too
3046 // this is required because most probably the mouse
3047 // is over this chart, blocking it from auto-refreshing
3048 if(master === false && (state.paused === true || state.selected === true))
3052 // get or create a chart state, given a DOM element
3053 NETDATA.chartState = function(element) {
3054 var state = $(element).data('netdata-state-object') || null;
3055 if(state === null) {
3056 state = new chartState(element);
3057 $(element).data('netdata-state-object', state);
3062 // ----------------------------------------------------------------------------------------------------------------
3063 // Library functions
3065 // Load a script without jquery
3066 // This is used to load jquery - after it is loaded, we use jquery
3067 NETDATA._loadjQuery = function(callback) {
3068 if(typeof jQuery === 'undefined') {
3069 if(NETDATA.options.debug.main_loop === true)
3070 console.log('loading ' + NETDATA.jQuery);
3072 var script = document.createElement('script');
3073 script.type = 'text/javascript';
3074 script.async = true;
3075 script.src = NETDATA.jQuery;
3077 // script.onabort = onError;
3078 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3079 if(typeof callback === "function")
3080 script.onload = callback;
3082 var s = document.getElementsByTagName('script')[0];
3083 s.parentNode.insertBefore(script, s);
3085 else if(typeof callback === "function")
3089 NETDATA._loadCSS = function(filename) {
3090 // don't use jQuery here
3091 // styles are loaded before jQuery
3092 // to eliminate showing an unstyled page to the user
3094 var fileref = document.createElement("link");
3095 fileref.setAttribute("rel", "stylesheet");
3096 fileref.setAttribute("type", "text/css");
3097 fileref.setAttribute("href", filename);
3099 if (typeof fileref !== 'undefined')
3100 document.getElementsByTagName("head")[0].appendChild(fileref);
3103 NETDATA.colorHex2Rgb = function(hex) {
3104 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3105 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3106 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3107 return r + r + g + g + b + b;
3110 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3112 r: parseInt(result[1], 16),
3113 g: parseInt(result[2], 16),
3114 b: parseInt(result[3], 16)
3118 NETDATA.colorLuminance = function(hex, lum) {
3119 // validate hex string
3120 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3122 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3126 // convert to decimal and change luminosity
3127 var rgb = "#", c, i;
3128 for (i = 0; i < 3; i++) {
3129 c = parseInt(hex.substr(i*2,2), 16);
3130 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3131 rgb += ("00"+c).substr(c.length);
3137 NETDATA.guid = function() {
3139 return Math.floor((1 + Math.random()) * 0x10000)
3144 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3147 NETDATA.zeropad = function(x) {
3148 if(x > -10 && x < 10) return '0' + x.toString();
3149 else return x.toString();
3152 // user function to signal us the DOM has been
3154 NETDATA.updatedDom = function() {
3155 NETDATA.options.updated_dom = true;
3158 NETDATA.ready = function(callback) {
3159 NETDATA.options.pauseCallback = callback;
3162 NETDATA.pause = function(callback) {
3163 if(NETDATA.options.pause === true)
3166 NETDATA.options.pauseCallback = callback;
3169 NETDATA.unpause = function() {
3170 NETDATA.options.pauseCallback = null;
3171 NETDATA.options.updated_dom = true;
3172 NETDATA.options.pause = false;
3175 // ----------------------------------------------------------------------------------------------------------------
3177 // this is purely sequencial charts refresher
3178 // it is meant to be autonomous
3179 NETDATA.chartRefresherNoParallel = function(index) {
3180 if(NETDATA.options.debug.mail_loop === true)
3181 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3183 if(NETDATA.options.updated_dom === true) {
3184 // the dom has been updated
3185 // get the dom parts again
3186 NETDATA.parseDom(NETDATA.chartRefresher);
3189 if(index >= NETDATA.options.targets.length) {
3190 if(NETDATA.options.debug.main_loop === true)
3191 console.log('waiting to restart main loop...');
3193 NETDATA.options.auto_refresher_fast_weight = 0;
3195 setTimeout(function() {
3196 NETDATA.chartRefresher();
3197 }, NETDATA.options.current.idle_between_loops);
3200 var state = NETDATA.options.targets[index];
3202 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3203 if(NETDATA.options.debug.main_loop === true)
3204 console.log('fast rendering...');
3206 state.autoRefresh(function() {
3207 NETDATA.chartRefresherNoParallel(++index);
3211 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3212 NETDATA.options.auto_refresher_fast_weight = 0;
3214 setTimeout(function() {
3215 state.autoRefresh(function() {
3216 NETDATA.chartRefresherNoParallel(++index);
3218 }, NETDATA.options.current.idle_between_charts);
3223 // this is part of the parallel refresher
3224 // its cause is to refresh sequencially all the charts
3225 // that depend on chart library initialization
3226 // it will call the parallel refresher back
3227 // as soon as it sees a chart that its chart library
3229 NETDATA.chartRefresher_uninitialized = function() {
3230 if(NETDATA.options.updated_dom === true) {
3231 // the dom has been updated
3232 // get the dom parts again
3233 NETDATA.parseDom(NETDATA.chartRefresher);
3237 if(NETDATA.options.sequencial.length === 0)
3238 NETDATA.chartRefresher();
3240 var state = NETDATA.options.sequencial.pop();
3241 if(state.library.initialized === true)
3242 NETDATA.chartRefresher();
3244 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3248 NETDATA.chartRefresherWaitTime = function() {
3249 return NETDATA.options.current.idle_parallel_loops;
3252 // the default refresher
3253 // it will create 2 sets of charts:
3254 // - the ones that can be refreshed in parallel
3255 // - the ones that depend on something else
3256 // the first set will be executed in parallel
3257 // the second will be given to NETDATA.chartRefresher_uninitialized()
3258 NETDATA.chartRefresher = function() {
3259 if(NETDATA.options.pause === true) {
3260 // console.log('auto-refresher is paused');
3261 setTimeout(NETDATA.chartRefresher,
3262 NETDATA.chartRefresherWaitTime());
3266 if(typeof NETDATA.options.pauseCallback === 'function') {
3267 // console.log('auto-refresher is calling pauseCallback');
3268 NETDATA.options.pause = true;
3269 NETDATA.options.pauseCallback();
3270 NETDATA.chartRefresher();
3274 if(NETDATA.options.current.parallel_refresher === false) {
3275 NETDATA.chartRefresherNoParallel(0);
3279 if(NETDATA.options.updated_dom === true) {
3280 // the dom has been updated
3281 // get the dom parts again
3282 NETDATA.parseDom(NETDATA.chartRefresher);
3286 var parallel = new Array();
3287 var targets = NETDATA.options.targets;
3288 var len = targets.length;
3291 state = targets[len];
3292 if(state.isVisible() === false || state.running === true)
3295 if(state.library.initialized === false) {
3296 if(state.library.enabled === true) {
3297 state.library.initialize(NETDATA.chartRefresher);
3301 state.error('chart library "' + state.library_name + '" is not enabled.');
3305 parallel.unshift(state);
3308 if(parallel.length > 0) {
3309 // this will execute the jobs in parallel
3310 $(parallel).each(function() {
3315 // run the next refresh iteration
3316 setTimeout(NETDATA.chartRefresher,
3317 NETDATA.chartRefresherWaitTime());
3320 NETDATA.parseDom = function(callback) {
3321 NETDATA.options.last_page_scroll = new Date().getTime();
3322 NETDATA.options.updated_dom = false;
3324 var targets = $('div[data-netdata]'); //.filter(':visible');
3326 if(NETDATA.options.debug.main_loop === true)
3327 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3329 NETDATA.options.targets = new Array();
3330 var len = targets.length;
3332 // the initialization will take care of sizing
3333 // and the "loading..." message
3334 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3337 if(typeof callback === 'function') callback();
3340 // this is the main function - where everything starts
3341 NETDATA.start = function() {
3342 // this should be called only once
3344 NETDATA.options.page_is_visible = true;
3346 $(window).blur(function() {
3347 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3348 NETDATA.options.page_is_visible = false;
3349 if(NETDATA.options.debug.focus === true)
3350 console.log('Lost Focus!');
3354 $(window).focus(function() {
3355 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3356 NETDATA.options.page_is_visible = true;
3357 if(NETDATA.options.debug.focus === true)
3358 console.log('Focus restored!');
3362 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3363 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3364 NETDATA.options.page_is_visible = false;
3365 if(NETDATA.options.debug.focus === true)
3366 console.log('Document has no focus!');
3370 // bootstrap tab switching
3371 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3373 // bootstrap modal switching
3374 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3375 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3377 NETDATA.parseDom(NETDATA.chartRefresher);
3380 // ----------------------------------------------------------------------------------------------------------------
3383 NETDATA.peityInitialize = function(callback) {
3384 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3386 url: NETDATA.peity_js,
3391 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3394 NETDATA.chartLibraries.peity.enabled = false;
3395 NETDATA.error(100, NETDATA.peity_js);
3397 .always(function() {
3398 if(typeof callback === "function")
3403 NETDATA.chartLibraries.peity.enabled = false;
3404 if(typeof callback === "function")
3409 NETDATA.peityChartUpdate = function(state, data) {
3410 state.peity_instance.innerHTML = data.result;
3412 if(state.peity_options.stroke !== state.chartColors()[0]) {
3413 state.peity_options.stroke = state.chartColors()[0];
3414 if(state.chart.chart_type === 'line')
3415 state.peity_options.fill = NETDATA.themes.current.background;
3417 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3420 $(state.peity_instance).peity('line', state.peity_options);
3424 NETDATA.peityChartCreate = function(state, data) {
3425 state.peity_instance = document.createElement('div');
3426 state.element_chart.appendChild(state.peity_instance);
3428 var self = $(state.element);
3429 state.peity_options = {
3430 stroke: NETDATA.themes.current.foreground,
3431 strokeWidth: self.data('peity-strokewidth') || 1,
3432 width: state.chartWidth(),
3433 height: state.chartHeight(),
3434 fill: NETDATA.themes.current.foreground
3437 NETDATA.peityChartUpdate(state, data);
3441 // ----------------------------------------------------------------------------------------------------------------
3444 NETDATA.sparklineInitialize = function(callback) {
3445 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3447 url: NETDATA.sparkline_js,
3452 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3455 NETDATA.chartLibraries.sparkline.enabled = false;
3456 NETDATA.error(100, NETDATA.sparkline_js);
3458 .always(function() {
3459 if(typeof callback === "function")
3464 NETDATA.chartLibraries.sparkline.enabled = false;
3465 if(typeof callback === "function")
3470 NETDATA.sparklineChartUpdate = function(state, data) {
3471 state.sparkline_options.width = state.chartWidth();
3472 state.sparkline_options.height = state.chartHeight();
3474 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3478 NETDATA.sparklineChartCreate = function(state, data) {
3479 var self = $(state.element);
3480 var type = self.data('sparkline-type') || 'line';
3481 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3482 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3483 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3484 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3485 var composite = self.data('sparkline-composite') || undefined;
3486 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3487 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3488 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3489 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3490 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3491 var spotColor = self.data('sparkline-spotcolor') || undefined;
3492 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3493 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3494 var spotRadius = self.data('sparkline-spotradius') || undefined;
3495 var valueSpots = self.data('sparkline-valuespots') || undefined;
3496 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3497 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3498 var lineWidth = self.data('sparkline-linewidth') || undefined;
3499 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3500 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3501 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3502 var xvalues = self.data('sparkline-xvalues') || undefined;
3503 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3504 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3505 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3506 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3507 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3508 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3509 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3510 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3511 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3512 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3513 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3514 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3515 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3516 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3517 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3518 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3519 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3520 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3521 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3522 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3523 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3524 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3526 state.sparkline_options = {
3528 lineColor: lineColor,
3529 fillColor: fillColor,
3530 chartRangeMin: chartRangeMin,
3531 chartRangeMax: chartRangeMax,
3532 composite: composite,
3533 enableTagOptions: enableTagOptions,
3534 tagOptionPrefix: tagOptionPrefix,
3535 tagValuesAttribute: tagValuesAttribute,
3536 disableHiddenCheck: disableHiddenCheck,
3537 defaultPixelsPerValue: defaultPixelsPerValue,
3538 spotColor: spotColor,
3539 minSpotColor: minSpotColor,
3540 maxSpotColor: maxSpotColor,
3541 spotRadius: spotRadius,
3542 valueSpots: valueSpots,
3543 highlightSpotColor: highlightSpotColor,
3544 highlightLineColor: highlightLineColor,
3545 lineWidth: lineWidth,
3546 normalRangeMin: normalRangeMin,
3547 normalRangeMax: normalRangeMax,
3548 drawNormalOnTop: drawNormalOnTop,
3550 chartRangeClip: chartRangeClip,
3551 chartRangeMinX: chartRangeMinX,
3552 chartRangeMaxX: chartRangeMaxX,
3553 disableInteraction: disableInteraction,
3554 disableTooltips: disableTooltips,
3555 disableHighlight: disableHighlight,
3556 highlightLighten: highlightLighten,
3557 highlightColor: highlightColor,
3558 tooltipContainer: tooltipContainer,
3559 tooltipClassname: tooltipClassname,
3560 tooltipChartTitle: state.title,
3561 tooltipFormat: tooltipFormat,
3562 tooltipPrefix: tooltipPrefix,
3563 tooltipSuffix: tooltipSuffix,
3564 tooltipSkipNull: tooltipSkipNull,
3565 tooltipValueLookups: tooltipValueLookups,
3566 tooltipFormatFieldlist: tooltipFormatFieldlist,
3567 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3568 numberFormatter: numberFormatter,
3569 numberDigitGroupSep: numberDigitGroupSep,
3570 numberDecimalMark: numberDecimalMark,
3571 numberDigitGroupCount: numberDigitGroupCount,
3572 animatedZooms: animatedZooms,
3573 width: state.chartWidth(),
3574 height: state.chartHeight()
3577 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3581 // ----------------------------------------------------------------------------------------------------------------
3588 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3589 if(after < state.netdata_first)
3590 after = state.netdata_first;
3592 if(before > state.netdata_last)
3593 before = state.netdata_last;
3595 state.setMode('zoom');
3596 state.globalSelectionSyncStop();
3597 state.globalSelectionSyncDelay();
3598 state.dygraph_user_action = true;
3599 state.dygraph_force_zoom = true;
3600 state.updateChartPanOrZoom(after, before);
3601 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3604 NETDATA.dygraphSetSelection = function(state, t) {
3605 if(typeof state.dygraph_instance !== 'undefined') {
3606 var r = state.calculateRowForTime(t);
3608 state.dygraph_instance.setSelection(r);
3610 state.dygraph_instance.clearSelection();
3611 state.legendShowUndefined();
3618 NETDATA.dygraphClearSelection = function(state, t) {
3619 if(typeof state.dygraph_instance !== 'undefined') {
3620 state.dygraph_instance.clearSelection();
3625 NETDATA.dygraphSmoothInitialize = function(callback) {
3627 url: NETDATA.dygraph_smooth_js,
3632 NETDATA.dygraph.smooth = true;
3633 smoothPlotter.smoothing = 0.3;
3636 NETDATA.dygraph.smooth = false;
3638 .always(function() {
3639 if(typeof callback === "function")
3644 NETDATA.dygraphInitialize = function(callback) {
3645 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3647 url: NETDATA.dygraph_js,
3652 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3655 NETDATA.chartLibraries.dygraph.enabled = false;
3656 NETDATA.error(100, NETDATA.dygraph_js);
3658 .always(function() {
3659 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3660 NETDATA.dygraphSmoothInitialize(callback);
3661 else if(typeof callback === "function")
3666 NETDATA.chartLibraries.dygraph.enabled = false;
3667 if(typeof callback === "function")
3672 NETDATA.dygraphChartUpdate = function(state, data) {
3673 var dygraph = state.dygraph_instance;
3675 if(typeof dygraph === 'undefined')
3676 return NETDATA.dygraphChartCreate(state, data);
3678 // when the chart is not visible, and hidden
3679 // if there is a window resize, dygraph detects
3680 // its element size as 0x0.
3681 // this will make it re-appear properly
3683 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3687 file: data.result.data,
3688 colors: state.chartColors(),
3689 labels: data.result.labels,
3690 labelsDivWidth: state.chartWidth() - 70,
3691 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3694 if(state.dygraph_force_zoom === true) {
3695 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3696 state.log('dygraphChartUpdate() forced zoom update');
3698 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3699 options.valueRange = null;
3700 options.isZoomedIgnoreProgrammaticZoom = true;
3701 state.dygraph_force_zoom = false;
3703 else if(state.current.name !== 'auto') {
3704 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3705 state.log('dygraphChartUpdate() loose update');
3708 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3709 state.log('dygraphChartUpdate() strict update');
3711 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3712 options.valueRange = null;
3713 options.isZoomedIgnoreProgrammaticZoom = true;
3716 if(state.dygraph_smooth_eligible === true) {
3717 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3718 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3719 NETDATA.dygraphChartCreate(state, data);
3724 dygraph.updateOptions(options);
3726 state.dygraph_last_rendered = new Date().getTime();
3730 NETDATA.dygraphChartCreate = function(state, data) {
3731 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3732 state.log('dygraphChartCreate()');
3734 var self = $(state.element);
3736 var chart_type = state.chart.chart_type;
3737 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3738 chart_type = self.data('dygraph-type') || chart_type;
3740 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3741 smooth = self.data('dygraph-smooth') || smooth;
3743 if(NETDATA.dygraph.smooth === false)
3746 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3747 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3749 state.dygraph_options = {
3750 colors: self.data('dygraph-colors') || state.chartColors(),
3752 // leave a few pixels empty on the right of the chart
3753 rightGap: self.data('dygraph-rightgap') || 5,
3754 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3755 showRoller: self.data('dygraph-showroller') || false,
3757 title: self.data('dygraph-title') || state.title,
3758 titleHeight: self.data('dygraph-titleheight') || 19,
3760 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3761 labels: data.result.labels,
3762 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3763 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3764 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3765 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3766 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3769 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3770 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3772 ylabel: state.units,
3773 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3775 // the function to plot the chart
3778 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3779 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3780 strokePattern: self.data('dygraph-strokepattern') || undefined,
3782 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3783 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3784 drawPoints: self.data('dygraph-drawpoints') || false,
3786 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3787 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3789 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3790 pointSize: self.data('dygraph-pointsize') || 1,
3792 // enabling this makes the chart with little square lines
3793 stepPlot: self.data('dygraph-stepplot') || false,
3795 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3796 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3797 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3799 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3800 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3801 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3802 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3804 drawAxis: self.data('dygraph-drawaxis') || true,
3805 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3806 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3807 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3809 drawGrid: self.data('dygraph-drawgrid') || true,
3810 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3811 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3812 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3813 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3814 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3816 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3817 sigFigs: self.data('dygraph-sigfigs') || null,
3818 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3819 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3821 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3822 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3823 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3825 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3826 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3830 ticker: Dygraph.dateTicker,
3831 axisLabelFormatter: function (d, gran) {
3832 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3834 valueFormatter: function (ms) {
3835 var d = new Date(ms);
3836 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3837 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3842 valueFormatter: function (x) {
3843 // we format legends with the state object
3844 // no need to do anything here
3845 // return (Math.round(x*100) / 100).toLocaleString();
3846 // return state.legendFormatValue(x);
3851 legendFormatter: function(data) {
3852 var elements = state.element_legend_childs;
3854 // if the hidden div is not there
3855 // we are not managing the legend
3856 if(elements.hidden === null) return;
3858 if (typeof data.x !== 'undefined') {
3859 state.legendSetDate(data.x);
3860 var i = data.series.length;
3862 var series = data.series[i];
3863 if(!series.isVisible) continue;
3864 state.legendSetLabelValue(series.label, series.y);
3870 drawCallback: function(dygraph, is_initial) {
3871 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3872 state.dygraph_user_action = false;
3874 var x_range = dygraph.xAxisRange();
3875 var after = Math.round(x_range[0]);
3876 var before = Math.round(x_range[1]);
3878 if(NETDATA.options.debug.dygraph === true)
3879 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3881 if(before <= state.netdata_last && after >= state.netdata_first)
3882 state.updateChartPanOrZoom(after, before);
3885 zoomCallback: function(minDate, maxDate, yRanges) {
3886 if(NETDATA.options.debug.dygraph === true)
3887 state.log('dygraphZoomCallback()');
3889 state.globalSelectionSyncStop();
3890 state.globalSelectionSyncDelay();
3891 state.setMode('zoom');
3893 // refresh it to the greatest possible zoom level
3894 state.dygraph_user_action = true;
3895 state.dygraph_force_zoom = true;
3896 state.updateChartPanOrZoom(minDate, maxDate);
3898 highlightCallback: function(event, x, points, row, seriesName) {
3899 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3900 state.log('dygraphHighlightCallback()');
3904 // there is a bug in dygraph when the chart is zoomed enough
3905 // the time it thinks is selected is wrong
3906 // here we calculate the time t based on the row number selected
3908 var t = state.data_after + row * state.data_update_every;
3909 // console.log('row = ' + row + ', x = ' + x + ', t = ' + t + ' ' + ((t === x)?'SAME':(Math.abs(x-t)<=state.data_update_every)?'SIMILAR':'DIFFERENT') + ', rows in db: ' + state.data_points + ' visible(x) = ' + state.timeIsVisible(x) + ' visible(t) = ' + state.timeIsVisible(t) + ' r(x) = ' + state.calculateRowForTime(x) + ' r(t) = ' + state.calculateRowForTime(t) + ' range: ' + state.data_after + ' - ' + state.data_before + ' real: ' + state.data.after + ' - ' + state.data.before + ' every: ' + state.data_update_every);
3911 state.globalSelectionSync(x);
3913 // fix legend zIndex using the internal structures of dygraph legend module
3914 // this works, but it is a hack!
3915 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
3917 unhighlightCallback: function(event) {
3918 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3919 state.log('dygraphUnhighlightCallback()');
3921 state.unpauseChart();
3922 state.globalSelectionSyncStop();
3924 interactionModel : {
3925 mousedown: function(event, dygraph, context) {
3926 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3927 state.log('interactionModel.mousedown()');
3929 state.dygraph_user_action = true;
3930 state.globalSelectionSyncStop();
3932 if(NETDATA.options.debug.dygraph === true)
3933 state.log('dygraphMouseDown()');
3935 // Right-click should not initiate a zoom.
3936 if(event.button && event.button === 2) return;
3938 context.initializeMouseDown(event, dygraph, context);
3940 if(event.button && event.button === 1) {
3941 if (event.altKey || event.shiftKey) {
3942 state.setMode('pan');
3943 state.globalSelectionSyncDelay();
3944 Dygraph.startPan(event, dygraph, context);
3947 state.setMode('zoom');
3948 state.globalSelectionSyncDelay();
3949 Dygraph.startZoom(event, dygraph, context);
3953 if (event.altKey || event.shiftKey) {
3954 state.setMode('zoom');
3955 state.globalSelectionSyncDelay();
3956 Dygraph.startZoom(event, dygraph, context);
3959 state.setMode('pan');
3960 state.globalSelectionSyncDelay();
3961 Dygraph.startPan(event, dygraph, context);
3965 mousemove: function(event, dygraph, context) {
3966 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3967 state.log('interactionModel.mousemove()');
3969 if(context.isPanning) {
3970 state.dygraph_user_action = true;
3971 state.globalSelectionSyncStop();
3972 state.globalSelectionSyncDelay();
3973 state.setMode('pan');
3974 Dygraph.movePan(event, dygraph, context);
3976 else if(context.isZooming) {
3977 state.dygraph_user_action = true;
3978 state.globalSelectionSyncStop();
3979 state.globalSelectionSyncDelay();
3980 state.setMode('zoom');
3981 Dygraph.moveZoom(event, dygraph, context);
3984 mouseup: function(event, dygraph, context) {
3985 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3986 state.log('interactionModel.mouseup()');
3988 if (context.isPanning) {
3989 state.dygraph_user_action = true;
3990 state.globalSelectionSyncDelay();
3991 Dygraph.endPan(event, dygraph, context);
3993 else if (context.isZooming) {
3994 state.dygraph_user_action = true;
3995 state.globalSelectionSyncDelay();
3996 Dygraph.endZoom(event, dygraph, context);
3999 click: function(event, dygraph, context) {
4000 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4001 state.log('interactionModel.click()');
4003 event.preventDefault();
4005 dblclick: function(event, dygraph, context) {
4006 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4007 state.log('interactionModel.dblclick()');
4008 NETDATA.resetAllCharts(state);
4010 mousewheel: function(event, dygraph, context) {
4011 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4012 state.log('interactionModel.mousewheel()');
4014 // Take the offset of a mouse event on the dygraph canvas and
4015 // convert it to a pair of percentages from the bottom left.
4016 // (Not top left, bottom is where the lower value is.)
4017 function offsetToPercentage(g, offsetX, offsetY) {
4018 // This is calculating the pixel offset of the leftmost date.
4019 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4020 var yar0 = g.yAxisRange(0);
4022 // This is calculating the pixel of the higest value. (Top pixel)
4023 var yOffset = g.toDomCoords(null, yar0[1])[1];
4025 // x y w and h are relative to the corner of the drawing area,
4026 // so that the upper corner of the drawing area is (0, 0).
4027 var x = offsetX - xOffset;
4028 var y = offsetY - yOffset;
4030 // This is computing the rightmost pixel, effectively defining the
4032 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4034 // This is computing the lowest pixel, effectively defining the height.
4035 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4037 // Percentage from the left.
4038 var xPct = w === 0 ? 0 : (x / w);
4039 // Percentage from the top.
4040 var yPct = h === 0 ? 0 : (y / h);
4042 // The (1-) part below changes it from "% distance down from the top"
4043 // to "% distance up from the bottom".
4044 return [xPct, (1-yPct)];
4047 // Adjusts [x, y] toward each other by zoomInPercentage%
4048 // Split it so the left/bottom axis gets xBias/yBias of that change and
4049 // tight/top gets (1-xBias)/(1-yBias) of that change.
4051 // If a bias is missing it splits it down the middle.
4052 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4053 xBias = xBias || 0.5;
4054 yBias = yBias || 0.5;
4056 function adjustAxis(axis, zoomInPercentage, bias) {
4057 var delta = axis[1] - axis[0];
4058 var increment = delta * zoomInPercentage;
4059 var foo = [increment * bias, increment * (1-bias)];
4061 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4064 var yAxes = g.yAxisRanges();
4066 for (var i = 0; i < yAxes.length; i++) {
4067 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4070 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4073 if(event.altKey || event.shiftKey) {
4074 state.dygraph_user_action = true;
4076 state.globalSelectionSyncStop();
4077 state.globalSelectionSyncDelay();
4079 // http://dygraphs.com/gallery/interaction-api.js
4080 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4081 var percentage = normal / 50;
4083 if (!(event.offsetX && event.offsetY)){
4084 event.offsetX = event.layerX - event.target.offsetLeft;
4085 event.offsetY = event.layerY - event.target.offsetTop;
4088 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4089 var xPct = percentages[0];
4090 var yPct = percentages[1];
4092 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4094 var after = new_x_range[0];
4095 var before = new_x_range[1];
4097 var first = state.netdata_first + state.data_update_every;
4098 var last = state.netdata_last + state.data_update_every;
4101 after -= (before - last);
4108 state.setMode('zoom');
4109 if(state.updateChartPanOrZoom(after, before) === true)
4110 dygraph.updateOptions({ dateWindow: [ after, before ] });
4112 event.preventDefault();
4115 touchstart: function(event, dygraph, context) {
4116 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4117 state.log('interactionModel.touchstart()');
4119 state.dygraph_user_action = true;
4120 state.setMode('zoom');
4123 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4125 // we overwrite the touch directions at the end, to overwrite
4126 // the internal default of dygraphs
4127 context.touchDirections = { x: true, y: false };
4129 state.dygraph_last_touch_start = new Date().getTime();
4130 state.dygraph_last_touch_move = 0;
4132 if(typeof event.touches[0].pageX === 'number')
4133 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4135 state.dygraph_last_touch_page_x = 0;
4137 touchmove: function(event, dygraph, context) {
4138 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4139 state.log('interactionModel.touchmove()');
4141 state.dygraph_user_action = true;
4142 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4144 state.dygraph_last_touch_move = new Date().getTime();
4146 touchend: function(event, dygraph, context) {
4147 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4148 state.log('interactionModel.touchend()');
4150 state.dygraph_user_action = true;
4151 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4153 // if it didn't move, it is a selection
4154 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4155 // internal api of dygraphs
4156 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4157 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4158 if(NETDATA.dygraphSetSelection(state, t) === true)
4159 state.globalSelectionSync(t);
4162 // if it was double tap within double click time, reset the charts
4163 var now = new Date().getTime();
4164 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4165 if(state.dygraph_last_touch_move === 0) {
4166 var dt = now - state.dygraph_last_touch_end;
4167 if(dt <= NETDATA.options.current.double_click_speed)
4168 NETDATA.resetAllCharts(state);
4172 // remember the timestamp of the last touch end
4173 state.dygraph_last_touch_end = now;
4178 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4179 state.dygraph_options.drawGrid = false;
4180 state.dygraph_options.drawAxis = false;
4181 state.dygraph_options.title = undefined;
4182 state.dygraph_options.units = undefined;
4183 state.dygraph_options.ylabel = undefined;
4184 state.dygraph_options.yLabelWidth = 0;
4185 state.dygraph_options.labelsDivWidth = 120;
4186 state.dygraph_options.labelsDivStyles.width = '120px';
4187 state.dygraph_options.labelsSeparateLines = true;
4188 state.dygraph_options.rightGap = 0;
4191 if(smooth === true) {
4192 state.dygraph_smooth_eligible = true;
4194 if(NETDATA.options.current.smooth_plot === true)
4195 state.dygraph_options.plotter = smoothPlotter;
4197 else state.dygraph_smooth_eligible = false;
4199 state.dygraph_instance = new Dygraph(state.element_chart,
4200 data.result.data, state.dygraph_options);
4202 state.dygraph_force_zoom = false;
4203 state.dygraph_user_action = false;
4204 state.dygraph_last_rendered = new Date().getTime();
4208 // ----------------------------------------------------------------------------------------------------------------
4211 NETDATA.morrisInitialize = function(callback) {
4212 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4214 // morris requires raphael
4215 if(!NETDATA.chartLibraries.raphael.initialized) {
4216 if(NETDATA.chartLibraries.raphael.enabled) {
4217 NETDATA.raphaelInitialize(function() {
4218 NETDATA.morrisInitialize(callback);
4222 NETDATA.chartLibraries.morris.enabled = false;
4223 if(typeof callback === "function")
4228 NETDATA._loadCSS(NETDATA.morris_css);
4231 url: NETDATA.morris_js,
4236 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4239 NETDATA.chartLibraries.morris.enabled = false;
4240 NETDATA.error(100, NETDATA.morris_js);
4242 .always(function() {
4243 if(typeof callback === "function")
4249 NETDATA.chartLibraries.morris.enabled = false;
4250 if(typeof callback === "function")
4255 NETDATA.morrisChartUpdate = function(state, data) {
4256 state.morris_instance.setData(data.result.data);
4260 NETDATA.morrisChartCreate = function(state, data) {
4262 state.morris_options = {
4263 element: state.element_chart.id,
4264 data: data.result.data,
4266 ykeys: data.dimension_names,
4267 labels: data.dimension_names,
4273 continuousLine: false,
4274 behaveLikeLine: false
4277 if(state.chart.chart_type === 'line')
4278 state.morris_instance = new Morris.Line(state.morris_options);
4280 else if(state.chart.chart_type === 'area') {
4281 state.morris_options.behaveLikeLine = true;
4282 state.morris_instance = new Morris.Area(state.morris_options);
4285 state.morris_instance = new Morris.Area(state.morris_options);
4290 // ----------------------------------------------------------------------------------------------------------------
4293 NETDATA.raphaelInitialize = function(callback) {
4294 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4296 url: NETDATA.raphael_js,
4301 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4304 NETDATA.chartLibraries.raphael.enabled = false;
4305 NETDATA.error(100, NETDATA.raphael_js);
4307 .always(function() {
4308 if(typeof callback === "function")
4313 NETDATA.chartLibraries.raphael.enabled = false;
4314 if(typeof callback === "function")
4319 NETDATA.raphaelChartUpdate = function(state, data) {
4320 $(state.element_chart).raphael(data.result, {
4321 width: state.chartWidth(),
4322 height: state.chartHeight()
4328 NETDATA.raphaelChartCreate = function(state, data) {
4329 $(state.element_chart).raphael(data.result, {
4330 width: state.chartWidth(),
4331 height: state.chartHeight()
4337 // ----------------------------------------------------------------------------------------------------------------
4340 NETDATA.c3Initialize = function(callback) {
4341 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4344 if(!NETDATA.chartLibraries.d3.initialized) {
4345 if(NETDATA.chartLibraries.d3.enabled) {
4346 NETDATA.d3Initialize(function() {
4347 NETDATA.c3Initialize(callback);
4351 NETDATA.chartLibraries.c3.enabled = false;
4352 if(typeof callback === "function")
4357 NETDATA._loadCSS(NETDATA.c3_css);
4365 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4368 NETDATA.chartLibraries.c3.enabled = false;
4369 NETDATA.error(100, NETDATA.c3_js);
4371 .always(function() {
4372 if(typeof callback === "function")
4378 NETDATA.chartLibraries.c3.enabled = false;
4379 if(typeof callback === "function")
4384 NETDATA.c3ChartUpdate = function(state, data) {
4385 state.c3_instance.destroy();
4386 return NETDATA.c3ChartCreate(state, data);
4388 //state.c3_instance.load({
4389 // rows: data.result,
4396 NETDATA.c3ChartCreate = function(state, data) {
4398 state.element_chart.id = 'c3-' + state.uuid;
4399 // console.log('id = ' + state.element_chart.id);
4401 state.c3_instance = c3.generate({
4402 bindto: '#' + state.element_chart.id,
4404 width: state.chartWidth(),
4405 height: state.chartHeight()
4408 pattern: state.chartColors()
4413 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4419 format: function(x) {
4420 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4447 // console.log(state.c3_instance);
4452 // ----------------------------------------------------------------------------------------------------------------
4455 NETDATA.d3Initialize = function(callback) {
4456 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4463 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4466 NETDATA.chartLibraries.d3.enabled = false;
4467 NETDATA.error(100, NETDATA.d3_js);
4469 .always(function() {
4470 if(typeof callback === "function")
4475 NETDATA.chartLibraries.d3.enabled = false;
4476 if(typeof callback === "function")
4481 NETDATA.d3ChartUpdate = function(state, data) {
4485 NETDATA.d3ChartCreate = function(state, data) {
4489 // ----------------------------------------------------------------------------------------------------------------
4492 NETDATA.googleInitialize = function(callback) {
4493 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4495 url: NETDATA.google_js,
4500 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4501 google.load('visualization', '1.1', {
4502 'packages': ['corechart', 'controls'],
4503 'callback': callback
4507 NETDATA.chartLibraries.google.enabled = false;
4508 NETDATA.error(100, NETDATA.google_js);
4509 if(typeof callback === "function")
4514 NETDATA.chartLibraries.google.enabled = false;
4515 if(typeof callback === "function")
4520 NETDATA.googleChartUpdate = function(state, data) {
4521 var datatable = new google.visualization.DataTable(data.result);
4522 state.google_instance.draw(datatable, state.google_options);
4526 NETDATA.googleChartCreate = function(state, data) {
4527 var datatable = new google.visualization.DataTable(data.result);
4529 state.google_options = {
4530 colors: state.chartColors(),
4532 // do not set width, height - the chart resizes itself
4533 //width: state.chartWidth(),
4534 //height: state.chartHeight(),
4539 // title: "Time of Day",
4540 // format:'HH:mm:ss',
4541 viewWindowMode: 'maximized',
4553 viewWindowMode: 'pretty',
4568 focusTarget: 'category',
4575 titlePosition: 'out',
4586 curveType: 'function',
4591 switch(state.chart.chart_type) {
4593 state.google_options.vAxis.viewWindowMode = 'maximized';
4594 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4595 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4599 state.google_options.isStacked = true;
4600 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4601 state.google_options.vAxis.viewWindowMode = 'maximized';
4602 state.google_options.vAxis.minValue = null;
4603 state.google_options.vAxis.maxValue = null;
4604 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4609 state.google_options.lineWidth = 2;
4610 state.google_instance = new google.visualization.LineChart(state.element_chart);
4614 state.google_instance.draw(datatable, state.google_options);
4618 // ----------------------------------------------------------------------------------------------------------------
4620 NETDATA.percentFromValueMax = function(value, max) {
4621 if(value === null) value = 0;
4622 if(max < value) max = value;
4626 pcent = Math.round(value * 100 / max);
4627 if(pcent === 0 && value > 0) pcent = 1;
4633 // ----------------------------------------------------------------------------------------------------------------
4636 NETDATA.easypiechartInitialize = function(callback) {
4637 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4639 url: NETDATA.easypiechart_js,
4644 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4647 NETDATA.chartLibraries.easypiechart.enabled = false;
4648 NETDATA.error(100, NETDATA.easypiechart_js);
4650 .always(function() {
4651 if(typeof callback === "function")
4656 NETDATA.chartLibraries.easypiechart.enabled = false;
4657 if(typeof callback === "function")
4662 NETDATA.easypiechartClearSelection = function(state) {
4663 if(typeof state.easyPieChartEvent !== 'undefined') {
4664 if(state.easyPieChartEvent.timer !== null)
4665 clearTimeout(state.easyPieChartEvent.timer);
4667 state.easyPieChartEvent.timer = null;
4670 if(state.isAutoRefreshable() === true && state.data !== null) {
4671 NETDATA.easypiechartChartUpdate(state, state.data);
4674 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4675 state.easyPieChart_instance.update(0);
4677 state.easyPieChart_instance.enableAnimation();
4682 NETDATA.easypiechartSetSelection = function(state, t) {
4683 if(state.timeIsVisible(t) !== true)
4684 return NETDATA.easypiechartClearSelection(state);
4686 var slot = state.calculateRowForTime(t);
4687 if(slot < 0 || slot >= state.data.result.length)
4688 return NETDATA.easypiechartClearSelection(state);
4690 if(typeof state.easyPieChartEvent === 'undefined') {
4691 state.easyPieChartEvent = {
4698 var value = state.data.result[state.data.result.length - 1 - slot];
4699 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4700 var pcent = NETDATA.percentFromValueMax(value, max);
4702 state.easyPieChartEvent.value = value;
4703 state.easyPieChartEvent.pcent = pcent;
4704 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4706 if(state.easyPieChartEvent.timer === null) {
4707 state.easyPieChart_instance.disableAnimation();
4709 state.easyPieChartEvent.timer = setTimeout(function() {
4710 state.easyPieChartEvent.timer = null;
4711 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4712 }, NETDATA.options.current.charts_selection_animation_delay);
4718 NETDATA.easypiechartChartUpdate = function(state, data) {
4719 var value, max, pcent;
4721 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
4727 value = data.result[0];
4728 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4729 pcent = NETDATA.percentFromValueMax(value, max);
4732 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4733 state.easyPieChart_instance.update(pcent);
4737 NETDATA.easypiechartChartCreate = function(state, data) {
4738 var self = $(state.element);
4739 var chart = $(state.element_chart);
4741 var value = data.result[0];
4742 var max = self.data('easypiechart-max-value') || null;
4743 var adjust = self.data('easypiechart-adjust') || null;
4747 state.easyPieChartMax = null;
4750 state.easyPieChartMax = max;
4752 var pcent = NETDATA.percentFromValueMax(value, max);
4754 chart.data('data-percent', pcent);
4758 case 'width': size = state.chartHeight(); break;
4759 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4760 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4762 default: size = state.chartWidth(); break;
4764 state.element.style.width = size + 'px';
4765 state.element.style.height = size + 'px';
4767 var stroke = Math.floor(size / 22);
4768 if(stroke < 3) stroke = 2;
4770 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4771 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4772 state.easyPieChartLabel = document.createElement('span');
4773 state.easyPieChartLabel.className = 'easyPieChartLabel';
4774 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4775 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4776 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4777 state.element_chart.appendChild(state.easyPieChartLabel);
4779 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4780 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4781 state.easyPieChartTitle = document.createElement('span');
4782 state.easyPieChartTitle.className = 'easyPieChartTitle';
4783 state.easyPieChartTitle.innerHTML = state.title;
4784 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4785 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4786 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4787 state.element_chart.appendChild(state.easyPieChartTitle);
4789 var unitfontsize = Math.round(titlefontsize * 0.9);
4790 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4791 state.easyPieChartUnits = document.createElement('span');
4792 state.easyPieChartUnits.className = 'easyPieChartUnits';
4793 state.easyPieChartUnits.innerHTML = state.units;
4794 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4795 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4796 state.element_chart.appendChild(state.easyPieChartUnits);
4798 chart.easyPieChart({
4799 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4800 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4801 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4802 scaleLength: self.data('easypiechart-scalelength') || 5,
4803 lineCap: self.data('easypiechart-linecap') || 'round',
4804 lineWidth: self.data('easypiechart-linewidth') || stroke,
4805 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4806 size: self.data('easypiechart-size') || size,
4807 rotate: self.data('easypiechart-rotate') || 0,
4808 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4809 easing: self.data('easypiechart-easing') || undefined
4812 // when we just re-create the chart
4813 // do not animate the first update
4815 if(typeof state.easyPieChart_instance !== 'undefined')
4818 state.easyPieChart_instance = chart.data('easyPieChart');
4819 if(animate === false) state.easyPieChart_instance.disableAnimation();
4820 state.easyPieChart_instance.update(pcent);
4821 if(animate === false) state.easyPieChart_instance.enableAnimation();
4825 // ----------------------------------------------------------------------------------------------------------------
4828 NETDATA.gaugeInitialize = function(callback) {
4829 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4831 url: NETDATA.gauge_js,
4836 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4839 NETDATA.chartLibraries.gauge.enabled = false;
4840 NETDATA.error(100, NETDATA.gauge_js);
4842 .always(function() {
4843 if(typeof callback === "function")
4848 NETDATA.chartLibraries.gauge.enabled = false;
4849 if(typeof callback === "function")
4854 NETDATA.gaugeAnimation = function(state, status) {
4857 if(typeof status === 'boolean' && status === false)
4859 else if(typeof status === 'number')
4862 state.gauge_instance.animationSpeed = speed;
4863 state.___gaugeOld__.speed = speed;
4866 NETDATA.gaugeSet = function(state, value, min, max) {
4867 if(typeof value !== 'number') value = 0;
4868 if(typeof min !== 'number') min = 0;
4869 if(typeof max !== 'number') max = 0;
4870 if(value > max) max = value;
4871 if(value < min) min = value;
4880 // gauge.js has an issue if the needle
4881 // is smaller than min or larger than max
4882 // when we set the new values
4883 // the needle will go crazy
4885 // to prevent it, we always feed it
4886 // with a percentage, so that the needle
4887 // is always between min and max
4888 var pcent = (value - min) * 100 / (max - min);
4890 // these should never happen
4891 if(pcent < 0) pcent = 0;
4892 if(pcent > 100) pcent = 100;
4894 state.gauge_instance.set(pcent);
4896 state.___gaugeOld__.value = value;
4897 state.___gaugeOld__.min = min;
4898 state.___gaugeOld__.max = max;
4901 NETDATA.gaugeSetLabels = function(state, value, min, max) {
4902 if(state.___gaugeOld__.valueLabel !== value) {
4903 state.___gaugeOld__.valueLabel = value;
4904 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
4906 if(state.___gaugeOld__.minLabel !== min) {
4907 state.___gaugeOld__.minLabel = min;
4908 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
4910 if(state.___gaugeOld__.maxLabel !== max) {
4911 state.___gaugeOld__.maxLabel = max;
4912 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
4916 NETDATA.gaugeClearSelection = function(state) {
4917 if(typeof state.gaugeEvent !== 'undefined') {
4918 if(state.gaugeEvent.timer !== null)
4919 clearTimeout(state.gaugeEvent.timer);
4921 state.gaugeEvent.timer = null;
4924 if(state.isAutoRefreshable() === true && state.data !== null) {
4925 NETDATA.gaugeChartUpdate(state, state.data);
4928 NETDATA.gaugeAnimation(state, false);
4929 NETDATA.gaugeSet(state, null, null, null);
4930 NETDATA.gaugeSetLabels(state, null, null, null);
4933 NETDATA.gaugeAnimation(state, true);
4937 NETDATA.gaugeSetSelection = function(state, t) {
4938 if(state.timeIsVisible(t) !== true)
4939 return NETDATA.gaugeClearSelection(state);
4941 var slot = state.calculateRowForTime(t);
4942 if(slot < 0 || slot >= state.data.result.length)
4943 return NETDATA.gaugeClearSelection(state);
4945 if(typeof state.gaugeEvent === 'undefined') {
4946 state.gaugeEvent = {
4954 var value = state.data.result[state.data.result.length - 1 - slot];
4955 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
4958 state.gaugeEvent.value = value;
4959 state.gaugeEvent.max = max;
4960 state.gaugeEvent.min = min;
4961 NETDATA.gaugeSetLabels(state, value, min, max);
4963 if(state.gaugeEvent.timer === null) {
4964 NETDATA.gaugeAnimation(state, false);
4966 state.gaugeEvent.timer = setTimeout(function() {
4967 state.gaugeEvent.timer = null;
4968 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
4969 }, NETDATA.options.current.charts_selection_animation_delay);
4975 NETDATA.gaugeChartUpdate = function(state, data) {
4976 var value, min, max;
4978 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
4982 NETDATA.gaugeSetLabels(state, null, null, null);
4985 value = data.result[0];
4987 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
4988 if(value > max) max = value;
4989 NETDATA.gaugeSetLabels(state, value, min, max);
4992 NETDATA.gaugeSet(state, value, min, max);
4996 NETDATA.gaugeChartCreate = function(state, data) {
4997 var self = $(state.element);
4998 // var chart = $(state.element_chart);
5000 var value = data.result[0];
5001 var max = self.data('gauge-max-value') || null;
5002 var adjust = self.data('gauge-adjust') || null;
5003 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5004 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5005 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5006 var stopColor = self.data('gauge-stop-color') || void 0;
5007 var generateGradient = self.data('gauge-generate-gradient') || false;
5011 state.gaugeMax = null;
5014 state.gaugeMax = max;
5016 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5018 // case 'width': width = height * ratio; break;
5020 // default: height = width / ratio; break;
5022 //state.element.style.width = width.toString() + 'px';
5023 //state.element.style.height = height.toString() + 'px';
5028 lines: 12, // The number of lines to draw
5029 angle: 0.15, // The length of each line
5030 lineWidth: 0.44, // 0.44 The line thickness
5032 length: 0.8, // 0.9 The radius of the inner circle
5033 strokeWidth: 0.035, // The rotation offset
5034 color: pointerColor // Fill color
5036 colorStart: startColor, // Colors
5037 colorStop: stopColor, // just experiment with them
5038 strokeColor: strokeColor, // to see which ones work best for you
5040 generateGradient: generateGradient,
5044 if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5045 options.percentColors = [
5046 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5047 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5048 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5049 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5050 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5051 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5052 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5053 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5054 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5055 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5056 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5059 state.gauge_canvas = document.createElement('canvas');
5060 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5061 state.gauge_canvas.className = 'gaugeChart';
5062 state.gauge_canvas.width = width;
5063 state.gauge_canvas.height = height;
5064 state.element_chart.appendChild(state.gauge_canvas);
5066 var valuefontsize = Math.floor(height / 6);
5067 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5068 state.gaugeChartLabel = document.createElement('span');
5069 state.gaugeChartLabel.className = 'gaugeChartLabel';
5070 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5071 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5072 state.element_chart.appendChild(state.gaugeChartLabel);
5074 var titlefontsize = Math.round(valuefontsize / 2);
5076 state.gaugeChartTitle = document.createElement('span');
5077 state.gaugeChartTitle.className = 'gaugeChartTitle';
5078 state.gaugeChartTitle.innerHTML = state.title;
5079 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5080 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5081 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5082 state.element_chart.appendChild(state.gaugeChartTitle);
5084 var unitfontsize = Math.round(titlefontsize * 0.9);
5085 state.gaugeChartUnits = document.createElement('span');
5086 state.gaugeChartUnits.className = 'gaugeChartUnits';
5087 state.gaugeChartUnits.innerHTML = state.units;
5088 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5089 state.element_chart.appendChild(state.gaugeChartUnits);
5091 state.gaugeChartMin = document.createElement('span');
5092 state.gaugeChartMin.className = 'gaugeChartMin';
5093 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5094 state.element_chart.appendChild(state.gaugeChartMin);
5096 state.gaugeChartMax = document.createElement('span');
5097 state.gaugeChartMax.className = 'gaugeChartMax';
5098 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5099 state.element_chart.appendChild(state.gaugeChartMax);
5101 // when we just re-create the chart
5102 // do not animate the first update
5104 if(typeof state.gauge_instance !== 'undefined')
5107 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5109 state.___gaugeOld__ = {
5118 // we will always feed a percentage
5119 state.gauge_instance.minValue = 0;
5120 state.gauge_instance.maxValue = 100;
5122 NETDATA.gaugeAnimation(state, animate);
5123 NETDATA.gaugeSet(state, value, 0, max);
5124 NETDATA.gaugeSetLabels(state, value, 0, max);
5125 NETDATA.gaugeAnimation(state, true);
5129 // ----------------------------------------------------------------------------------------------------------------
5130 // Charts Libraries Registration
5132 NETDATA.chartLibraries = {
5134 initialize: NETDATA.dygraphInitialize,
5135 create: NETDATA.dygraphChartCreate,
5136 update: NETDATA.dygraphChartUpdate,
5137 resize: function(state) {
5138 if(typeof state.dygraph_instance.resize === 'function')
5139 state.dygraph_instance.resize();
5141 setSelection: NETDATA.dygraphSetSelection,
5142 clearSelection: NETDATA.dygraphClearSelection,
5143 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5146 format: function(state) { return 'json'; },
5147 options: function(state) { return 'ms|flip'; },
5148 legend: function(state) {
5149 if(this.isSparkline(state) === false)
5150 return 'right-side';
5154 autoresize: function(state) { return true; },
5155 max_updates_to_recreate: function(state) { return 5000; },
5156 track_colors: function(state) { return true; },
5157 pixels_per_point: function(state) {
5158 if(this.isSparkline(state) === false)
5164 isSparkline: function(state) {
5165 if(typeof state.dygraph_sparkline === 'undefined') {
5166 var t = $(state.element).data('dygraph-theme');
5167 if(t === 'sparkline')
5168 state.dygraph_sparkline = true;
5170 state.dygraph_sparkline = false;
5172 return state.dygraph_sparkline;
5176 initialize: NETDATA.sparklineInitialize,
5177 create: NETDATA.sparklineChartCreate,
5178 update: NETDATA.sparklineChartUpdate,
5180 setSelection: undefined, // function(state, t) { return true; },
5181 clearSelection: undefined, // function(state) { return true; },
5182 toolboxPanAndZoom: null,
5185 format: function(state) { return 'array'; },
5186 options: function(state) { return 'flip|abs'; },
5187 legend: function(state) { return null; },
5188 autoresize: function(state) { return false; },
5189 max_updates_to_recreate: function(state) { return 5000; },
5190 track_colors: function(state) { return false; },
5191 pixels_per_point: function(state) { return 3; }
5194 initialize: NETDATA.peityInitialize,
5195 create: NETDATA.peityChartCreate,
5196 update: NETDATA.peityChartUpdate,
5198 setSelection: undefined, // function(state, t) { return true; },
5199 clearSelection: undefined, // function(state) { return true; },
5200 toolboxPanAndZoom: null,
5203 format: function(state) { return 'ssvcomma'; },
5204 options: function(state) { return 'null2zero|flip|abs'; },
5205 legend: function(state) { return null; },
5206 autoresize: function(state) { return false; },
5207 max_updates_to_recreate: function(state) { return 5000; },
5208 track_colors: function(state) { return false; },
5209 pixels_per_point: function(state) { return 3; }
5212 initialize: NETDATA.morrisInitialize,
5213 create: NETDATA.morrisChartCreate,
5214 update: NETDATA.morrisChartUpdate,
5216 setSelection: undefined, // function(state, t) { return true; },
5217 clearSelection: undefined, // function(state) { return true; },
5218 toolboxPanAndZoom: null,
5221 format: function(state) { return 'json'; },
5222 options: function(state) { return 'objectrows|ms'; },
5223 legend: function(state) { return null; },
5224 autoresize: function(state) { return false; },
5225 max_updates_to_recreate: function(state) { return 50; },
5226 track_colors: function(state) { return false; },
5227 pixels_per_point: function(state) { return 15; }
5230 initialize: NETDATA.googleInitialize,
5231 create: NETDATA.googleChartCreate,
5232 update: NETDATA.googleChartUpdate,
5234 setSelection: undefined, //function(state, t) { return true; },
5235 clearSelection: undefined, //function(state) { return true; },
5236 toolboxPanAndZoom: null,
5239 format: function(state) { return 'datatable'; },
5240 options: function(state) { return ''; },
5241 legend: function(state) { return null; },
5242 autoresize: function(state) { return false; },
5243 max_updates_to_recreate: function(state) { return 300; },
5244 track_colors: function(state) { return false; },
5245 pixels_per_point: function(state) { return 4; }
5248 initialize: NETDATA.raphaelInitialize,
5249 create: NETDATA.raphaelChartCreate,
5250 update: NETDATA.raphaelChartUpdate,
5252 setSelection: undefined, // function(state, t) { return true; },
5253 clearSelection: undefined, // function(state) { return true; },
5254 toolboxPanAndZoom: null,
5257 format: function(state) { return 'json'; },
5258 options: function(state) { return ''; },
5259 legend: function(state) { return null; },
5260 autoresize: function(state) { return false; },
5261 max_updates_to_recreate: function(state) { return 5000; },
5262 track_colors: function(state) { return false; },
5263 pixels_per_point: function(state) { return 3; }
5266 initialize: NETDATA.c3Initialize,
5267 create: NETDATA.c3ChartCreate,
5268 update: NETDATA.c3ChartUpdate,
5270 setSelection: undefined, // function(state, t) { return true; },
5271 clearSelection: undefined, // function(state) { return true; },
5272 toolboxPanAndZoom: null,
5275 format: function(state) { return 'csvjsonarray'; },
5276 options: function(state) { return 'milliseconds'; },
5277 legend: function(state) { return null; },
5278 autoresize: function(state) { return false; },
5279 max_updates_to_recreate: function(state) { return 5000; },
5280 track_colors: function(state) { return false; },
5281 pixels_per_point: function(state) { return 15; }
5284 initialize: NETDATA.d3Initialize,
5285 create: NETDATA.d3ChartCreate,
5286 update: NETDATA.d3ChartUpdate,
5288 setSelection: undefined, // function(state, t) { return true; },
5289 clearSelection: undefined, // function(state) { return true; },
5290 toolboxPanAndZoom: null,
5293 format: function(state) { return 'json'; },
5294 options: function(state) { return ''; },
5295 legend: function(state) { return null; },
5296 autoresize: function(state) { return false; },
5297 max_updates_to_recreate: function(state) { return 5000; },
5298 track_colors: function(state) { return false; },
5299 pixels_per_point: function(state) { return 3; }
5302 initialize: NETDATA.easypiechartInitialize,
5303 create: NETDATA.easypiechartChartCreate,
5304 update: NETDATA.easypiechartChartUpdate,
5306 setSelection: NETDATA.easypiechartSetSelection,
5307 clearSelection: NETDATA.easypiechartClearSelection,
5308 toolboxPanAndZoom: null,
5311 format: function(state) { return 'array'; },
5312 options: function(state) { return 'absolute'; },
5313 legend: function(state) { return null; },
5314 autoresize: function(state) { return false; },
5315 max_updates_to_recreate: function(state) { return 5000; },
5316 track_colors: function(state) { return true; },
5317 pixels_per_point: function(state) { return 3; },
5321 initialize: NETDATA.gaugeInitialize,
5322 create: NETDATA.gaugeChartCreate,
5323 update: NETDATA.gaugeChartUpdate,
5325 setSelection: NETDATA.gaugeSetSelection,
5326 clearSelection: NETDATA.gaugeClearSelection,
5327 toolboxPanAndZoom: null,
5330 format: function(state) { return 'array'; },
5331 options: function(state) { return 'absolute'; },
5332 legend: function(state) { return null; },
5333 autoresize: function(state) { return false; },
5334 max_updates_to_recreate: function(state) { return 5000; },
5335 track_colors: function(state) { return true; },
5336 pixels_per_point: function(state) { return 3; },
5341 NETDATA.registerChartLibrary = function(library, url) {
5342 if(NETDATA.options.debug.libraries === true)
5343 console.log("registering chart library: " + library);
5345 NETDATA.chartLibraries[library].url = url;
5346 NETDATA.chartLibraries[library].initialized = true;
5347 NETDATA.chartLibraries[library].enabled = true;
5350 // ----------------------------------------------------------------------------------------------------------------
5353 NETDATA.requiredJs = [
5355 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5356 isAlreadyLoaded: function() {
5357 // check if bootstrap is loaded
5358 if(typeof $().emulateTransitionEnd == 'function')
5361 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5369 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5370 isAlreadyLoaded: function() { return false; }
5373 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5374 isAlreadyLoaded: function() { return false; }
5378 NETDATA.requiredCSS = [
5380 url: NETDATA.themes.current.bootstrap_css,
5381 isAlreadyLoaded: function() {
5382 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5389 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5390 isAlreadyLoaded: function() { return false; }
5393 url: NETDATA.themes.current.dashboard_css,
5394 isAlreadyLoaded: function() { return false; }
5397 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5398 isAlreadyLoaded: function() { return false; }
5402 NETDATA.loadRequiredJs = function(index, callback) {
5403 if(index >= NETDATA.requiredJs.length) {
5404 if(typeof callback === 'function')
5409 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5410 NETDATA.loadRequiredJs(++index, callback);
5414 if(NETDATA.options.debug.main_loop === true)
5415 console.log('loading ' + NETDATA.requiredJs[index].url);
5418 url: NETDATA.requiredJs[index].url,
5422 .success(function() {
5423 if(NETDATA.options.debug.main_loop === true)
5424 console.log('loaded ' + NETDATA.requiredJs[index].url);
5426 NETDATA.loadRequiredJs(++index, callback);
5429 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5433 NETDATA.loadRequiredCSS = function(index) {
5434 if(index >= NETDATA.requiredCSS.length)
5437 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5438 NETDATA.loadRequiredCSS(++index);
5442 if(NETDATA.options.debug.main_loop === true)
5443 console.log('loading ' + NETDATA.requiredCSS[index].url);
5445 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5446 NETDATA.loadRequiredCSS(++index);
5449 NETDATA.errorReset();
5450 NETDATA.loadRequiredCSS(0);
5452 NETDATA._loadjQuery(function() {
5453 NETDATA.loadRequiredJs(0, function() {
5454 if(typeof $().emulateTransitionEnd !== 'function') {
5455 // bootstrap is not available
5456 NETDATA.options.current.show_help = false;
5459 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
5460 if(NETDATA.options.debug.main_loop === true)
5461 console.log('starting chart refresh thread');
5468 // window.NETDATA = NETDATA;
5469 // })(window, document);