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
15 // You can also set the default netdata server, using the following.
16 // When this variable is not set, we assume the page is hosted on your
17 // netdata server already.
18 // var netdataServer = "http://yourhost:19999"; // set your NetData server
20 //(function(window, document, undefined) {
21 // fix IE issue with console
22 if(!window.console){ window.console = {log: function(){} }; }
25 var NETDATA = window.NETDATA || {};
27 // ----------------------------------------------------------------------------------------------------------------
28 // Detect the netdata server
30 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
31 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
32 NETDATA._scriptSource = function() {
35 if(typeof document.currentScript !== 'undefined') {
36 script = document.currentScript;
39 var all_scripts = document.getElementsByTagName('script');
40 script = all_scripts[all_scripts.length - 1];
43 if (typeof script.getAttribute.length !== 'undefined')
46 script = script.getAttribute('src', -1);
51 if(typeof netdataServer !== 'undefined')
52 NETDATA.serverDefault = netdataServer;
54 var s = NETDATA._scriptSource();
55 NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
58 if(NETDATA.serverDefault === null)
59 NETDATA.serverDefault = '';
60 else if(NETDATA.serverDefault.slice(-1) !== '/')
61 NETDATA.serverDefault += '/';
63 // default URLs for all the external files we need
64 // make them RELATIVE so that the whole thing can also be
65 // installed under a web server
66 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-1.12.0.min.js';
67 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity.min.js';
68 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline.min.js';
69 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart.min.js';
70 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge.min.js';
71 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined.js';
72 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter.js';
73 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-min.js';
74 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris.min.js';
75 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3.min.js';
76 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3.min.js';
77 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3.min.css';
78 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris.css';
79 NETDATA.google_js = 'https://www.google.com/jsapi';
83 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.min.css',
84 dashboard_css: NETDATA.serverDefault + 'dashboard.css',
85 background: '#FFFFFF',
86 foreground: '#000000',
89 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
90 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
91 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
92 '#329262', '#3B3EAC' ],
93 easypiechart_track: '#f0f0f0',
94 easypiechart_scale: '#dfe0e0',
95 gauge_pointer: '#C0C0C0',
96 gauge_stroke: '#F0F0F0',
100 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.slate.min.css',
101 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css',
102 background: '#272b30',
103 foreground: '#C8C8C8',
106 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
107 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
108 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
109 '#a6a479', '#a66da8' ],
111 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
112 '#3B3EAC', '#EE9911', '#BB44CC', '#C83E3E', '#990099', '#CC7700',
113 '#22AA99', '#109618', '#6633CC', '#DD4477', '#316395', '#8B0707',
114 '#329262', '#3B3EFF' ],
115 easypiechart_track: '#373b40',
116 easypiechart_scale: '#373b40',
117 gauge_pointer: '#474b50',
118 gauge_stroke: '#373b40',
119 gauge_gradient: false
123 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
124 NETDATA.themes.current = NETDATA.themes[netdataTheme];
126 NETDATA.themes.current = NETDATA.themes.default;
128 NETDATA.colors = NETDATA.themes.current.colors;
130 // these are the colors Google Charts are using
131 // we have them here to attempt emulate their look and feel on the other chart libraries
132 // http://there4.io/2012/05/02/google-chart-color-list/
133 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
134 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
135 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
137 // an alternative set
138 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
139 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
140 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
142 // ----------------------------------------------------------------------------------------------------------------
143 // the defaults for all charts
145 // if the user does not specify any of these, the following will be used
147 NETDATA.chartDefaults = {
148 host: NETDATA.serverDefault, // the server to get data from
149 width: '100%', // the chart width - can be null
150 height: '100%', // the chart height - can be null
151 min_width: null, // the chart minimum width - can be null
152 library: 'dygraph', // the graphing library to use
153 method: 'average', // the grouping method
154 before: 0, // panning
155 after: -600, // panning
156 pixels_per_point: 1, // the detail of the chart
157 fill_luminance: 0.8 // luminance of colors in solit areas
160 // ----------------------------------------------------------------------------------------------------------------
164 pauseCallback: null, // a callback when we are really paused
166 pause: false, // when enabled we don't auto-refresh the charts
168 targets: null, // an array of all the state objects that are
169 // currently active (independently of their
170 // viewport visibility)
172 updated_dom: true, // when true, the DOM has been updated with
173 // new elements we have to check.
175 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
176 // rendering charts continiously.
177 // used with .current.fast_render_timeframe
179 page_is_visible: true, // when true, this page is visible
181 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
182 // auto-refresher for some time (when a chart is
183 // performing pan or zoom, we need to stop refreshing
184 // all other charts, to have the maximum speed for
185 // rendering the chart that is panned or zoomed).
186 // Used with .current.global_pan_sync_time
188 last_resized: new Date().getTime(), // the timestamp of the last resize request
190 crossDomainAjax: false, // enable this to request crossDomain AJAX
192 last_page_scroll: 0, // the timestamp the last time the page was scrolled
194 // the current profile
195 // we may have many...
197 pixels_per_point: 1, // the minimum pixels per point for all charts
198 // increase this to speed javascript up
199 // each chart library has its own limit too
200 // the max of this and the chart library is used
201 // the final is calculated every time, so a change
202 // here will have immediate effect on the next chart
205 idle_between_charts: 100, // ms - how much time to wait between chart updates
207 fast_render_timeframe: 200, // ms - render continously until this time of continious
208 // rendering has been reached
209 // this setting is used to make it render e.g. 10
210 // charts at once, sleep idle_between_charts time
211 // and continue for another 10 charts.
213 idle_between_loops: 500, // ms - if all charts have been updated, wait this
214 // time before starting again.
216 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
218 idle_lost_focus: 500, // ms - when the window does not have focus, check
219 // if focus has been regained, every this time
221 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
222 // autorefreshing of charts is paused for this amount
225 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
226 // of time before setting up synchronized selections
229 sync_selection: true, // enable or disable selection sync
231 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
233 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
235 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
237 update_only_visible: true, // enable or disable visibility management
239 parallel_refresher: true, // enable parallel refresh of charts
241 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
243 destroy_on_hide: false, // destroy charts when they are not visible
245 show_help: true, // when enabled the charts will show some help
246 show_help_delay_show_ms: 500,
247 show_help_delay_hide_ms: 0,
249 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
251 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
252 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
254 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
256 smooth_plot: true, // enable smooth plot, where possible
258 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
260 color_fill_opacity_line: 1.0,
261 color_fill_opacity_area: 0.2,
262 color_fill_opacity_stacked: 0.8,
264 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
265 pan_and_zoom_factor_multiplier_control: 2.0,
266 pan_and_zoom_factor_multiplier_shift: 3.0,
267 pan_and_zoom_factor_multiplier_alt: 4.0,
269 setOptionCallback: function() { ; }
277 chart_data_url: false,
278 chart_errors: false, // FIXME
287 // ----------------------------------------------------------------------------------------------------------------
288 // local storage options
290 NETDATA.localStorage = {
293 callback: {} // only used for resetting back to defaults
296 NETDATA.localStorageGet = function(key, def, callback) {
299 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
300 NETDATA.localStorage.default[key.toString()] = def;
301 NETDATA.localStorage.callback[key.toString()] = callback;
304 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
306 // console.log('localStorage: loading "' + key.toString() + '"');
307 ret = localStorage.getItem(key.toString());
308 if(ret === null || ret === 'undefined') {
309 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
310 localStorage.setItem(key.toString(), JSON.stringify(def));
314 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
315 ret = JSON.parse(ret);
316 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
320 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
325 if(typeof ret === 'undefined' || ret === 'undefined') {
326 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
330 NETDATA.localStorage.current[key.toString()] = ret;
334 NETDATA.localStorageSet = function(key, value, callback) {
335 if(typeof value === 'undefined' || value === 'undefined') {
336 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
339 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
340 NETDATA.localStorage.default[key.toString()] = value;
341 NETDATA.localStorage.current[key.toString()] = value;
342 NETDATA.localStorage.callback[key.toString()] = callback;
345 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
346 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
348 localStorage.setItem(key.toString(), JSON.stringify(value));
351 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
355 NETDATA.localStorage.current[key.toString()] = value;
359 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
361 if(typeof obj[i] === 'object') {
362 //console.log('object ' + prefix + '.' + i.toString());
363 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
367 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
371 NETDATA.setOption = function(key, value) {
372 if(key.toString() === 'setOptionCallback') {
373 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
374 NETDATA.options.current[key.toString()] = value;
375 NETDATA.options.current.setOptionCallback();
378 else if(NETDATA.options.current[key.toString()] !== value) {
379 var name = 'options.' + key.toString();
381 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
382 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
384 //console.log(NETDATA.localStorage);
385 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
386 //console.log(NETDATA.options);
387 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
389 if(typeof NETDATA.options.current.setOptionCallback === 'function')
390 NETDATA.options.current.setOptionCallback();
396 NETDATA.getOption = function(key) {
397 return NETDATA.options.current[key.toString()];
400 // read settings from local storage
401 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
403 // always start with this option enabled.
404 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
406 NETDATA.resetOptions = function() {
407 for(var i in NETDATA.localStorage.default) {
408 var a = i.split('.');
410 if(a[0] === 'options') {
411 if(a[1] === 'setOptionCallback') continue;
412 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
413 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
415 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
417 else if(a[0] === 'chart_heights') {
418 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
419 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
425 // ----------------------------------------------------------------------------------------------------------------
427 if(NETDATA.options.debug.main_loop === true)
428 console.log('welcome to NETDATA');
430 NETDATA.onresize = function() {
431 NETDATA.options.last_resized = new Date().getTime();
435 NETDATA.onscroll = function() {
436 // console.log('onscroll');
438 NETDATA.options.last_page_scroll = new Date().getTime();
439 if(NETDATA.options.targets === null) return;
441 // when the user scrolls he sees that we have
442 // hidden all the not-visible charts
443 // using this little function we try to switch
444 // the charts back to visible quickly
445 var targets = NETDATA.options.targets;
446 var len = targets.length;
447 while(len--) targets[len].isVisible();
450 window.onresize = NETDATA.onresize;
451 window.onscroll = NETDATA.onscroll;
453 // ----------------------------------------------------------------------------------------------------------------
456 NETDATA.errorCodes = {
457 100: { message: "Cannot load chart library", alert: true },
458 101: { message: "Cannot load jQuery", alert: true },
459 402: { message: "Chart library not found", alert: false },
460 403: { message: "Chart library not enabled/is failed", alert: false },
461 404: { message: "Chart not found", alert: false }
463 NETDATA.errorLast = {
469 NETDATA.error = function(code, msg) {
470 NETDATA.errorLast.code = code;
471 NETDATA.errorLast.message = msg;
472 NETDATA.errorLast.datetime = new Date().getTime();
474 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
476 if(NETDATA.errorCodes[code].alert)
477 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
480 NETDATA.errorReset = function() {
481 NETDATA.errorLast.code = 0;
482 NETDATA.errorLast.message = "You are doing fine!";
483 NETDATA.errorLast.datetime = 0;
486 // ----------------------------------------------------------------------------------------------------------------
489 // When multiple charts need the same chart, we avoid downloading it
490 // multiple times (and having it in browser memory multiple time)
491 // by using this registry.
493 // Every time we download a chart definition, we save it here with .add()
494 // Then we try to get it back with .get(). If that fails, we download it.
496 NETDATA.chartRegistry = {
499 fixid: function(id) {
500 return id.replace(/:/g, "_").replace(/\//g, "_");
503 add: function(host, id, data) {
504 host = this.fixid(host);
507 if(typeof this.charts[host] === 'undefined')
508 this.charts[host] = {};
510 //console.log('added ' + host + '/' + id);
511 this.charts[host][id] = data;
514 get: function(host, id) {
515 host = this.fixid(host);
518 if(typeof this.charts[host] === 'undefined')
521 if(typeof this.charts[host][id] === 'undefined')
524 //console.log('cached ' + host + '/' + id);
525 return this.charts[host][id];
528 downloadAll: function(host, callback) {
529 while(host.slice(-1) === '/')
530 host = host.substring(0, host.length - 1);
535 url: host + '/api/v1/charts',
536 crossDomain: NETDATA.options.crossDomainAjax,
540 .done(function(data) {
542 var h = NETDATA.chartRegistry.fixid(host);
543 //console.log('downloaded all charts from ' + host + ' (' + h + ')');
544 self.charts[h] = data.charts;
546 if(typeof callback === 'function')
550 if(typeof callback === 'function')
556 // ----------------------------------------------------------------------------------------------------------------
557 // Global Pan and Zoom on charts
559 // Using this structure are synchronize all the charts, so that
560 // when you pan or zoom one, all others are automatically refreshed
561 // to the same timespan.
563 NETDATA.globalPanAndZoom = {
564 seq: 0, // timestamp ms
565 // every time a chart is panned or zoomed
566 // we set the timestamp here
567 // then we use it as a sequence number
568 // to find if other charts are syncronized
571 master: null, // the master chart (state), to which all others
574 force_before_ms: null, // the timespan to sync all other charts
575 force_after_ms: null,
578 setMaster: function(state, after, before) {
579 if(NETDATA.options.current.sync_pan_and_zoom === false)
582 if(this.master !== null && this.master !== state)
583 this.master.resetChart(true, true);
585 var now = new Date().getTime();
588 this.force_after_ms = after;
589 this.force_before_ms = before;
590 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
594 clearMaster: function() {
595 if(this.master !== null) {
596 var st = this.master;
603 this.force_after_ms = null;
604 this.force_before_ms = null;
605 NETDATA.options.auto_refresher_stop_until = 0;
608 // is the given state the master of the global
609 // pan and zoom sync?
610 isMaster: function(state) {
611 if(this.master === state) return true;
615 // are we currently have a global pan and zoom sync?
616 isActive: function() {
617 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
621 // check if a chart, other than the master
622 // needs to be refreshed, due to the global pan and zoom
623 shouldBeAutoRefreshed: function(state) {
624 if(this.master === null || this.seq === 0)
627 //if(state.needsRecreation())
630 if(state.tm.pan_and_zoom_seq === this.seq)
637 // ----------------------------------------------------------------------------------------------------------------
638 // dimensions selection
641 // move color assignment to dimensions, here
643 dimensionStatus = function(parent, label, name_div, value_div, color) {
644 this.enabled = false;
645 this.parent = parent;
647 this.name_div = null;
648 this.value_div = null;
649 this.color = NETDATA.themes.current.foreground;
651 if(parent.selected_count > parent.unselected_count)
652 this.selected = true;
654 this.selected = false;
656 this.setOptions(name_div, value_div, color);
659 dimensionStatus.prototype.invalidate = function() {
660 this.name_div = null;
661 this.value_div = null;
662 this.enabled = false;
665 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
668 if(this.name_div != name_div) {
669 this.name_div = name_div;
670 this.name_div.title = this.label;
671 this.name_div.style.color = this.color;
672 if(this.selected === false)
673 this.name_div.className = 'netdata-legend-name not-selected';
675 this.name_div.className = 'netdata-legend-name selected';
678 if(this.value_div != value_div) {
679 this.value_div = value_div;
680 this.value_div.title = this.label;
681 this.value_div.style.color = this.color;
682 if(this.selected === false)
683 this.value_div.className = 'netdata-legend-value not-selected';
685 this.value_div.className = 'netdata-legend-value selected';
692 dimensionStatus.prototype.setHandler = function() {
693 if(this.enabled === false) return;
697 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
698 this.name_div.onclick = this.value_div.onclick = function(e) {
700 if(ds.isSelected()) {
702 if(e.shiftKey === true || e.ctrlKey === true) {
703 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
706 if(ds.parent.countSelected() === 0)
707 ds.parent.selectAll();
710 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
711 if(ds.parent.countSelected() === 1) {
712 ds.parent.selectAll();
715 ds.parent.selectNone();
721 // this is not selected
722 if(e.shiftKey === true || e.ctrlKey === true) {
723 // control or shift key is pressed -> select this too
727 // no key is pressed -> select only this
728 ds.parent.selectNone();
733 ds.parent.state.redrawChart();
737 dimensionStatus.prototype.select = function() {
738 if(this.enabled === false) return;
740 this.name_div.className = 'netdata-legend-name selected';
741 this.value_div.className = 'netdata-legend-value selected';
742 this.selected = true;
745 dimensionStatus.prototype.unselect = function() {
746 if(this.enabled === false) return;
748 this.name_div.className = 'netdata-legend-name not-selected';
749 this.value_div.className = 'netdata-legend-value hidden';
750 this.selected = false;
753 dimensionStatus.prototype.isSelected = function() {
754 return(this.enabled === true && this.selected === true);
757 // ----------------------------------------------------------------------------------------------------------------
759 dimensionsVisibility = function(state) {
762 this.dimensions = {};
763 this.selected_count = 0;
764 this.unselected_count = 0;
767 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
768 if(typeof this.dimensions[label] === 'undefined') {
770 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
773 this.dimensions[label].setOptions(name_div, value_div, color);
775 return this.dimensions[label];
778 dimensionsVisibility.prototype.dimensionGet = function(label) {
779 return this.dimensions[label];
782 dimensionsVisibility.prototype.invalidateAll = function() {
783 for(var d in this.dimensions)
784 this.dimensions[d].invalidate();
787 dimensionsVisibility.prototype.selectAll = function() {
788 for(var d in this.dimensions)
789 this.dimensions[d].select();
792 dimensionsVisibility.prototype.countSelected = function() {
794 for(var d in this.dimensions)
795 if(this.dimensions[d].isSelected()) i++;
800 dimensionsVisibility.prototype.selectNone = function() {
801 for(var d in this.dimensions)
802 this.dimensions[d].unselect();
805 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
806 var ret = new Array();
807 this.selected_count = 0;
808 this.unselected_count = 0;
810 for(var i = 0, len = array.length; i < len ; i++) {
811 var ds = this.dimensions[array[i]];
812 if(typeof ds === 'undefined') {
813 // console.log(array[i] + ' is not found');
818 if(ds.isSelected()) {
820 this.selected_count++;
824 this.unselected_count++;
828 if(this.selected_count === 0 && this.unselected_count !== 0) {
830 return this.selected2BooleanArray(array);
837 // ----------------------------------------------------------------------------------------------------------------
838 // global selection sync
840 NETDATA.globalSelectionSync = {
847 if(this.state !== null)
848 this.state.globalSelectionSyncStop();
852 if(this.state !== null) {
853 this.state.globalSelectionSyncDelay();
858 // ----------------------------------------------------------------------------------------------------------------
859 // Our state object, where all per-chart values are stored
861 chartState = function(element) {
862 var self = $(element);
863 this.element = element;
866 // all private functions should use 'that', instead of 'this'
870 * show an error instead of the chart
872 var error = function(msg) {
873 that.element.innerHTML = that.id + ': ' + msg;
874 that.enabled = false;
875 that.current = that.pan;
878 // GUID - a unique identifier for the chart
879 this.uuid = NETDATA.guid();
881 // string - the name of chart
882 this.id = self.data('netdata');
884 // string - the key for localStorage settings
885 this.settings_id = self.data('id') || null;
887 // the user given dimensions of the element
888 this.width = self.data('width') || NETDATA.chartDefaults.width;
889 this.height = self.data('height') || NETDATA.chartDefaults.height;
891 if(this.settings_id !== null) {
892 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
893 // this is the callback that will be called
894 // if and when the user resets all localStorage variables
897 resizeChartToHeight(height);
901 // string - the netdata server URL, without any path
902 this.host = self.data('host') || NETDATA.chartDefaults.host;
904 // make sure the host does not end with /
905 // all netdata API requests use absolute paths
906 while(this.host.slice(-1) === '/')
907 this.host = this.host.substring(0, this.host.length - 1);
909 // string - the grouping method requested by the user
910 this.method = self.data('method') || NETDATA.chartDefaults.method;
912 // the time-range requested by the user
913 this.after = self.data('after') || NETDATA.chartDefaults.after;
914 this.before = self.data('before') || NETDATA.chartDefaults.before;
916 // the pixels per point requested by the user
917 this.pixels_per_point = self.data('pixels-per-point') || 1;
918 this.points = self.data('points') || null;
920 // the dimensions requested by the user
921 this.dimensions = self.data('dimensions') || null;
923 // the chart library requested by the user
924 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
926 // object - the chart library used
931 this.colors_assigned = {};
932 this.colors_available = null;
934 // the element already created by the user
935 this.element_message = null;
937 // the element with the chart
938 this.element_chart = null;
940 // the element with the legend of the chart (if created by us)
941 this.element_legend = null;
942 this.element_legend_childs = {
952 this.chart_url = null; // string - the url to download chart info
953 this.chart = null; // object - the chart as downloaded from the server
955 this.title = self.data('title') || null; // the title of the chart
956 this.units = self.data('units') || null; // the units of the chart dimensions
957 this.append_options = self.data('append-options') || null; // the units of the chart dimensions
959 this.validated = false; // boolean - has the chart been validated?
960 this.enabled = true; // boolean - is the chart enabled for refresh?
961 this.paused = false; // boolean - is the chart paused for any reason?
962 this.selected = false; // boolean - is the chart shown a selection?
963 this.debug = false; // boolean - console.log() debug info about this chart
965 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
966 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
967 this.requested_after = null; // milliseconds - the timestamp of the request after param
968 this.requested_before = null; // milliseconds - the timestamp of the request before param
969 this.requested_padding = null;
971 this.view_before = 0;
976 force_update_at: 0, // the timestamp to force the update at
977 force_before_ms: null,
983 force_update_at: 0, // the timestamp to force the update at
984 force_before_ms: null,
990 force_update_at: 0, // the timestamp to force the update at
991 force_before_ms: null,
995 // this is a pointer to one of the sub-classes below
997 this.current = this.auto;
999 // check the requested library is available
1000 // we don't initialize it here - it will be initialized when
1001 // this chart will be first used
1002 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1003 NETDATA.error(402, that.library_name);
1004 error('chart library "' + that.library_name + '" is not found');
1007 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1008 NETDATA.error(403, that.library_name);
1009 error('chart library "' + that.library_name + '" is not enabled');
1013 that.library = NETDATA.chartLibraries[that.library_name];
1015 // milliseconds - the time the last refresh took
1016 this.refresh_dt_ms = 0;
1018 // if we need to report the rendering speed
1019 // find the element that needs to be updated
1020 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1022 if(refresh_dt_element_name !== null)
1023 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1025 this.refresh_dt_element = null;
1027 this.dimensions_visibility = new dimensionsVisibility(this);
1029 this._updating = false;
1031 // ============================================================================================================
1032 // PRIVATE FUNCTIONS
1034 var createDOM = function() {
1035 if(that.enabled === false) return;
1037 if(that.element_message !== null) that.element_message.innerHTML = '';
1038 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1039 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1041 that.element.innerHTML = '';
1043 that.element_message = document.createElement('div');
1044 that.element_message.className = ' netdata-message hidden';
1045 that.element.appendChild(that.element_message);
1047 that.element_chart = document.createElement('div');
1048 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1049 that.element.appendChild(that.element_chart);
1051 if(that.hasLegend() === true) {
1052 that.element.className = "netdata-container-with-legend";
1053 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1055 that.element_legend = document.createElement('div');
1056 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1057 that.element.appendChild(that.element_legend);
1060 that.element.className = "netdata-container";
1061 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1063 that.element_legend = null;
1065 that.element_legend_childs.series = null;
1067 if(typeof(that.width) === 'string')
1068 $(that.element).css('width', that.width);
1069 else if(typeof(that.width) === 'number')
1070 $(that.element).css('width', that.width + 'px');
1072 if(typeof(that.library.aspect_ratio) === 'undefined') {
1073 if(typeof(that.height) === 'string')
1074 $(that.element).css('height', that.height);
1075 else if(typeof(that.height) === 'number')
1076 $(that.element).css('height', that.height + 'px');
1079 var w = that.element.offsetWidth;
1080 if(w === null || w === 0) {
1081 // the div is hidden
1082 // this is resize the chart when next viewed
1083 that.tm.last_resized = 0;
1086 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1089 if(NETDATA.chartDefaults.min_width !== null)
1090 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1092 that.tm.last_dom_created = new Date().getTime();
1098 * initialize state variables
1099 * destroy all (possibly) created state elements
1100 * create the basic DOM for a chart
1102 var init = function() {
1103 if(that.enabled === false) return;
1105 that.paused = false;
1106 that.selected = false;
1108 that.chart_created = false; // boolean - is the library.create() been called?
1109 that.updates_counter = 0; // numeric - the number of refreshes made so far
1110 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1111 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1114 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1115 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1116 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1118 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1119 last_updated: 0, // the timestamp the chart last updated with data
1120 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1122 // Used with NETDATA.globalPanAndZoom.seq
1123 last_visible_check: 0, // the time we last checked if it is visible
1124 last_resized: 0, // the time the chart was resized
1125 last_hidden: 0, // the time the chart was hidden
1126 last_unhidden: 0, // the time the chart was unhidden
1127 last_autorefreshed: 0 // the time the chart was last refreshed
1130 that.data = null; // the last data as downloaded from the netdata server
1131 that.data_url = 'invalid://'; // string - the last url used to update the chart
1132 that.data_points = 0; // number - the number of points returned from netdata
1133 that.data_after = 0; // milliseconds - the first timestamp of the data
1134 that.data_before = 0; // milliseconds - the last timestamp of the data
1135 that.data_update_every = 0; // milliseconds - the frequency to update the data
1137 that.tm.last_initialized = new Date().getTime();
1140 that.setMode('auto');
1143 var maxMessageFontSize = function() {
1144 // normally we want a font size, as tall as the element
1145 var h = that.element_message.clientHeight;
1147 // but give it some air, 20% let's say, or 5 pixels min
1148 var lost = Math.max(h * 0.2, 5);
1151 // center the text, verically
1152 var paddingTop = (lost - 5) / 2;
1154 // but check the width too
1155 // it should fit 10 characters in it
1156 var w = that.element_message.clientWidth / 10;
1158 paddingTop += (h - w) / 2;
1162 // and don't make it too huge
1163 // 5% of the screen size is good
1164 if(h > screen.height / 20) {
1165 paddingTop += (h - (screen.height / 20)) / 2;
1166 h = screen.height / 20;
1170 that.element_message.style.fontSize = h.toString() + 'px';
1171 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1174 var showMessage = function(msg) {
1175 that.element_message.className = 'netdata-message';
1176 that.element_message.innerHTML = msg;
1177 that.element_message.style.fontSize = 'x-small';
1178 that.element_message.style.paddingTop = '0px';
1179 that.___messageHidden___ = undefined;
1182 var showMessageIcon = function(icon) {
1183 that.element_message.innerHTML = icon;
1184 that.element_message.className = 'netdata-message icon';
1185 maxMessageFontSize();
1186 that.___messageHidden___ = undefined;
1189 var hideMessage = function() {
1190 if(typeof that.___messageHidden___ === 'undefined') {
1191 that.___messageHidden___ = true;
1192 that.element_message.className = 'netdata-message hidden';
1196 var showRendering = function() {
1198 if(that.chart !== null) {
1199 if(that.chart.chart_type === 'line')
1200 icon = '<i class="fa fa-line-chart"></i>';
1202 icon = '<i class="fa fa-area-chart"></i>';
1205 icon = '<i class="fa fa-area-chart"></i>';
1207 showMessageIcon(icon + ' netdata');
1210 var showLoading = function() {
1211 if(that.chart_created === false) {
1212 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1218 var isHidden = function() {
1219 if(typeof that.___chartIsHidden___ !== 'undefined')
1225 // hide the chart, when it is not visible - called from isVisible()
1226 var hideChart = function() {
1227 // hide it, if it is not already hidden
1228 if(isHidden() === true) return;
1230 if(that.chart_created === true) {
1231 // we should destroy it
1232 if(NETDATA.options.current.destroy_on_hide === true) {
1237 that.element_chart.style.display = 'none';
1238 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1239 that.tm.last_hidden = new Date().getTime();
1243 that.___chartIsHidden___ = true;
1246 // unhide the chart, when it is visible - called from isVisible()
1247 var unhideChart = function() {
1248 if(isHidden() === false) return;
1250 that.___chartIsHidden___ = undefined;
1251 that.updates_since_last_unhide = 0;
1253 if(that.chart_created === false) {
1254 // we need to re-initialize it, to show our background
1255 // logo in bootstrap tabs, until the chart loads
1259 that.tm.last_unhidden = new Date().getTime();
1260 that.element_chart.style.display = '';
1261 if(that.element_legend !== null) that.element_legend.style.display = '';
1267 var canBeRendered = function() {
1268 if(isHidden() === true || that.isVisible(true) === false)
1274 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1275 var callChartLibraryUpdateSafely = function(data) {
1278 if(canBeRendered() === false)
1281 if(NETDATA.options.debug.chart_errors === true)
1282 status = that.library.update(that, data);
1285 status = that.library.update(that, data);
1292 if(status === false) {
1293 error('chart failed to be updated as ' + that.library_name);
1300 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1301 var callChartLibraryCreateSafely = function(data) {
1304 if(canBeRendered() === false)
1307 if(NETDATA.options.debug.chart_errors === true)
1308 status = that.library.create(that, data);
1311 status = that.library.create(that, data);
1318 if(status === false) {
1319 error('chart failed to be created as ' + that.library_name);
1323 that.chart_created = true;
1324 that.updates_since_last_creation = 0;
1328 // ----------------------------------------------------------------------------------------------------------------
1331 // resizeChart() - private
1332 // to be called just before the chart library to make sure that
1333 // a properly sized dom is available
1334 var resizeChart = function() {
1335 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1336 if(that.chart_created === false) return;
1338 if(that.needsRecreation()) {
1341 else if(typeof that.library.resize === 'function') {
1342 that.library.resize(that);
1344 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1345 $(that.element_legend_childs.nano).nanoScroller();
1347 maxMessageFontSize();
1350 that.tm.last_resized = new Date().getTime();
1354 // this is the actual chart resize algorithm
1356 // - resize the entire container
1357 // - update the internal states
1358 // - resize the chart as the div changes height
1359 // - update the scrollbar of the legend
1360 var resizeChartToHeight = function(h) {
1362 that.element.style.height = h;
1364 if(that.settings_id !== null)
1365 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1367 var now = new Date().getTime();
1368 NETDATA.options.last_page_scroll = now;
1369 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1372 that.tm.last_resized = 0;
1376 this.resizeHandler = function(e) {
1379 if(typeof this.event_resize === 'undefined'
1380 || this.event_resize.chart_original_w === 'undefined'
1381 || this.event_resize.chart_original_h === 'undefined')
1382 this.event_resize = {
1383 chart_original_w: this.element.clientWidth,
1384 chart_original_h: this.element.clientHeight,
1388 if(e.type === 'touchstart') {
1389 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1390 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1393 this.event_resize.mouse_start_x = e.clientX;
1394 this.event_resize.mouse_start_y = e.clientY;
1397 this.event_resize.chart_start_w = this.element.clientWidth;
1398 this.event_resize.chart_start_h = this.element.clientHeight;
1399 this.event_resize.chart_last_w = this.element.clientWidth;
1400 this.event_resize.chart_last_h = this.element.clientHeight;
1402 var now = new Date().getTime();
1403 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1404 // double click / double tap event
1406 // the optimal height of the chart
1407 // showing the entire legend
1408 var optimal = this.event_resize.chart_last_h
1409 + this.element_legend_childs.content.scrollHeight
1410 - this.element_legend_childs.content.clientHeight;
1412 // if we are not optimal, be optimal
1413 if(this.event_resize.chart_last_h != optimal)
1414 resizeChartToHeight(optimal.toString() + 'px');
1416 // else if we do not have the original height
1417 // reset to the original height
1418 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1419 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1422 this.event_resize.last = now;
1424 // process movement event
1425 document.onmousemove =
1426 document.ontouchmove =
1427 this.element_legend_childs.resize_handler.onmousemove =
1428 this.element_legend_childs.resize_handler.ontouchmove =
1433 case 'mousemove': y = e.clientY; break;
1434 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1438 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1440 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1441 resizeChartToHeight(newH.toString() + 'px');
1442 that.event_resize.chart_last_h = newH;
1447 // process end event
1448 document.onmouseup =
1449 document.ontouchend =
1450 this.element_legend_childs.resize_handler.onmouseup =
1451 this.element_legend_childs.resize_handler.ontouchend =
1453 // remove all the hooks
1454 document.onmouseup =
1455 document.onmousemove =
1456 document.ontouchmove =
1457 document.ontouchend =
1458 that.element_legend_childs.resize_handler.onmousemove =
1459 that.element_legend_childs.resize_handler.ontouchmove =
1460 that.element_legend_childs.resize_handler.onmouseout =
1461 that.element_legend_childs.resize_handler.onmouseup =
1462 that.element_legend_childs.resize_handler.ontouchend =
1465 // allow auto-refreshes
1466 NETDATA.options.auto_refresher_stop_until = 0;
1472 var noDataToShow = function() {
1473 showMessageIcon('<i class="fa fa-warning"></i> empty');
1474 that.legendUpdateDOM();
1475 that.tm.last_autorefreshed = new Date().getTime();
1476 // that.data_update_every = 30 * 1000;
1477 //that.element_chart.style.display = 'none';
1478 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1479 //that.___chartIsHidden___ = true;
1482 // ============================================================================================================
1485 this.error = function(msg) {
1489 this.setMode = function(m) {
1490 if(this.current !== null && this.current.name === m) return;
1493 this.current = this.auto;
1494 else if(m === 'pan')
1495 this.current = this.pan;
1496 else if(m === 'zoom')
1497 this.current = this.zoom;
1499 this.current = this.auto;
1501 this.current.force_update_at = 0;
1502 this.current.force_before_ms = null;
1503 this.current.force_after_ms = null;
1505 this.tm.last_mode_switch = new Date().getTime();
1508 // ----------------------------------------------------------------------------------------------------------------
1509 // global selection sync
1511 // prevent to global selection sync for some time
1512 this.globalSelectionSyncDelay = function(ms) {
1513 if(NETDATA.options.current.sync_selection === false)
1516 if(typeof ms === 'number')
1517 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
1519 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
1522 // can we globally apply selection sync?
1523 this.globalSelectionSyncAbility = function() {
1524 if(NETDATA.options.current.sync_selection === false)
1527 if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
1533 this.globalSelectionSyncIsMaster = function() {
1534 if(NETDATA.globalSelectionSync.state === this)
1540 // this chart is the master of the global selection sync
1541 this.globalSelectionSyncBeMaster = function() {
1543 if(this.globalSelectionSyncIsMaster()) {
1544 if(this.debug === true)
1545 this.log('sync: I am the master already.');
1550 if(NETDATA.globalSelectionSync.state) {
1551 if(this.debug === true)
1552 this.log('sync: I am not the sync master. Resetting global sync.');
1554 this.globalSelectionSyncStop();
1557 // become the master
1558 if(this.debug === true)
1559 this.log('sync: becoming sync master.');
1561 this.selected = true;
1562 NETDATA.globalSelectionSync.state = this;
1564 // find the all slaves
1565 var targets = NETDATA.options.targets;
1566 var len = targets.length;
1571 if(this.debug === true)
1572 st.log('sync: not adding me to sync');
1574 else if(st.globalSelectionSyncIsEligible()) {
1575 if(this.debug === true)
1576 st.log('sync: adding to sync as slave');
1578 st.globalSelectionSyncBeSlave();
1582 // this.globalSelectionSyncDelay(100);
1585 // can the chart participate to the global selection sync as a slave?
1586 this.globalSelectionSyncIsEligible = function() {
1587 if(this.enabled === true
1588 && this.library !== null
1589 && typeof this.library.setSelection === 'function'
1590 && this.isVisible() === true
1591 && this.chart_created === true)
1597 // this chart becomes a slave of the global selection sync
1598 this.globalSelectionSyncBeSlave = function() {
1599 if(NETDATA.globalSelectionSync.state !== this)
1600 NETDATA.globalSelectionSync.slaves.push(this);
1603 // sync all the visible charts to the given time
1604 // this is to be called from the chart libraries
1605 this.globalSelectionSync = function(t) {
1606 if(this.globalSelectionSyncAbility() === false) {
1607 if(this.debug === true)
1608 this.log('sync: cannot sync (yet?).');
1613 if(this.globalSelectionSyncIsMaster() === false) {
1614 if(this.debug === true)
1615 this.log('sync: trying to be sync master.');
1617 this.globalSelectionSyncBeMaster();
1619 if(this.globalSelectionSyncAbility() === false) {
1620 if(this.debug === true)
1621 this.log('sync: cannot sync (yet?).');
1627 NETDATA.globalSelectionSync.last_t = t;
1628 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1633 // stop syncing all charts to the given time
1634 this.globalSelectionSyncStop = function() {
1635 if(NETDATA.globalSelectionSync.slaves.length) {
1636 if(this.debug === true)
1637 this.log('sync: cleaning up...');
1639 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1641 if(that.debug === true)
1642 st.log('sync: not adding me to sync stop');
1645 if(that.debug === true)
1646 st.log('sync: removed slave from sync');
1648 st.clearSelection();
1652 NETDATA.globalSelectionSync.last_t = 0;
1653 NETDATA.globalSelectionSync.slaves = [];
1654 NETDATA.globalSelectionSync.state = null;
1657 this.clearSelection();
1660 this.setSelection = function(t) {
1661 if(typeof this.library.setSelection === 'function') {
1662 if(this.library.setSelection(this, t) === true)
1663 this.selected = true;
1665 this.selected = false;
1667 else this.selected = true;
1669 if(this.selected === true && this.debug === true)
1670 this.log('selection set to ' + t.toString());
1672 return this.selected;
1675 this.clearSelection = function() {
1676 if(this.selected === true) {
1677 if(typeof this.library.clearSelection === 'function') {
1678 if(this.library.clearSelection(this) === true)
1679 this.selected = false;
1681 this.selected = true;
1683 else this.selected = false;
1685 if(this.selected === false && this.debug === true)
1686 this.log('selection cleared');
1691 return this.selected;
1694 // find if a timestamp (ms) is shown in the current chart
1695 this.timeIsVisible = function(t) {
1696 if(t >= this.data_after && t <= this.data_before)
1701 this.calculateRowForTime = function(t) {
1702 if(this.timeIsVisible(t) === false) return -1;
1703 return Math.floor((t - this.data_after) / this.data_update_every);
1706 // ----------------------------------------------------------------------------------------------------------------
1709 this.log = function(msg) {
1710 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
1713 this.pauseChart = function() {
1714 if(this.paused === false) {
1715 if(this.debug === true)
1716 this.log('pauseChart()');
1722 this.unpauseChart = function() {
1723 if(this.paused === true) {
1724 if(this.debug === true)
1725 this.log('unpauseChart()');
1727 this.paused = false;
1731 this.resetChart = function(dont_clear_master, dont_update) {
1732 if(this.debug === true)
1733 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
1735 if(typeof dont_clear_master === 'undefined')
1736 dont_clear_master = false;
1738 if(typeof dont_update === 'undefined')
1739 dont_update = false;
1741 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
1742 if(this.debug === true)
1743 this.log('resetChart() diverting to clearMaster().');
1744 // this will call us back with master === true
1745 NETDATA.globalPanAndZoom.clearMaster();
1749 this.clearSelection();
1751 this.tm.pan_and_zoom_seq = 0;
1753 this.setMode('auto');
1754 this.current.force_update_at = 0;
1755 this.current.force_before_ms = null;
1756 this.current.force_after_ms = null;
1757 this.tm.last_autorefreshed = 0;
1758 this.paused = false;
1759 this.selected = false;
1760 this.enabled = true;
1761 // this.debug = false;
1763 // do not update the chart here
1764 // or the chart will flip-flop when it is the master
1765 // of a selection sync and another chart becomes
1768 if(dont_update !== true && this.isVisible() === true) {
1773 this.updateChartPanOrZoom = function(after, before) {
1774 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
1777 if(this.debug === true)
1780 if(before < after) {
1781 if(this.debug === true)
1782 this.log(logme + 'flipped parameters, rejecting it.');
1787 if(typeof this.fixed_min_duration === 'undefined')
1788 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1790 var min_duration = this.fixed_min_duration;
1791 var current_duration = Math.round(this.view_before - this.view_after);
1793 // round the numbers
1794 after = Math.round(after);
1795 before = Math.round(before);
1797 // align them to update_every
1798 // stretching them further away
1799 after -= after % this.data_update_every;
1800 before += this.data_update_every - (before % this.data_update_every);
1802 // the final wanted duration
1803 var wanted_duration = before - after;
1805 // to allow panning, accept just a point below our minimum
1806 if((current_duration - this.data_update_every) < min_duration)
1807 min_duration = current_duration - this.data_update_every;
1809 // we do it, but we adjust to minimum size and return false
1810 // when the wanted size is below the current and the minimum
1812 if(wanted_duration < current_duration && wanted_duration < min_duration) {
1813 if(this.debug === true)
1814 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1816 min_duration = this.fixed_min_duration;
1818 var dt = (min_duration - wanted_duration) / 2;
1821 wanted_duration = before - after;
1825 var tolerance = this.data_update_every * 2;
1826 var movement = Math.abs(before - this.view_before);
1828 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
1829 if(this.debug === true)
1830 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);
1834 if(this.current.name === 'auto') {
1835 this.log(logme + 'caller called me with mode: ' + this.current.name);
1836 this.setMode('pan');
1839 if(this.debug === true)
1840 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);
1842 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1843 this.current.force_after_ms = after;
1844 this.current.force_before_ms = before;
1845 NETDATA.globalPanAndZoom.setMaster(this, after, before);
1849 this.legendFormatValue = function(value) {
1850 if(value === null || value === 'undefined') return '-';
1851 if(typeof value !== 'number') return value;
1853 var abs = Math.abs(value);
1854 if(abs >= 1000) return (Math.round(value)).toLocaleString();
1855 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1856 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
1857 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1858 return (Math.round(value * 10000) / 10000).toLocaleString();
1861 this.legendSetLabelValue = function(label, value) {
1862 var series = this.element_legend_childs.series[label];
1863 if(typeof series === 'undefined') return;
1864 if(series.value === null && series.user === null) return;
1866 // if the value has not changed, skip DOM update
1867 //if(series.last === value) return;
1870 if(typeof value === 'number') {
1871 var v = Math.abs(value);
1872 s = r = this.legendFormatValue(value);
1874 if(typeof series.last === 'number') {
1875 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1876 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1877 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1879 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1884 series.last = value;
1887 if(series.value !== null) series.value.innerHTML = s;
1888 if(series.user !== null) series.user.innerHTML = r;
1891 this.legendSetDate = function(ms) {
1892 if(typeof ms !== 'number') {
1893 this.legendShowUndefined();
1897 var d = new Date(ms);
1899 if(this.element_legend_childs.title_date)
1900 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
1902 if(this.element_legend_childs.title_time)
1903 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
1905 if(this.element_legend_childs.title_units)
1906 this.element_legend_childs.title_units.innerHTML = this.units;
1909 this.legendShowUndefined = function() {
1910 if(this.element_legend_childs.title_date)
1911 this.element_legend_childs.title_date.innerHTML = ' ';
1913 if(this.element_legend_childs.title_time)
1914 this.element_legend_childs.title_time.innerHTML = this.chart.name;
1916 if(this.element_legend_childs.title_units)
1917 this.element_legend_childs.title_units.innerHTML = ' ';
1919 if(this.data && this.element_legend_childs.series !== null) {
1920 var labels = this.data.dimension_names;
1921 var i = labels.length;
1923 var label = labels[i];
1925 if(typeof label === 'undefined') continue;
1926 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1927 this.legendSetLabelValue(label, null);
1932 this.legendShowLatestValues = function() {
1933 if(this.chart === null) return;
1934 if(this.selected) return;
1936 if(this.data === null || this.element_legend_childs.series === null) {
1937 this.legendShowUndefined();
1941 var show_undefined = true;
1942 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
1943 show_undefined = false;
1945 if(show_undefined) {
1946 this.legendShowUndefined();
1950 this.legendSetDate(this.view_before);
1952 var labels = this.data.dimension_names;
1953 var i = labels.length;
1955 var label = labels[i];
1957 if(typeof label === 'undefined') continue;
1958 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1961 this.legendSetLabelValue(label, null);
1963 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
1967 this.legendReset = function() {
1968 this.legendShowLatestValues();
1971 // this should be called just ONCE per dimension per chart
1972 this._chartDimensionColor = function(label) {
1973 if(this.colors === null) this.chartColors();
1975 if(typeof this.colors_assigned[label] === 'undefined') {
1976 if(this.colors_available.length === 0) {
1977 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
1978 this.colors_available.push(NETDATA.themes.current.colors[i]);
1981 this.colors_assigned[label] = this.colors_available.shift();
1983 if(this.debug === true)
1984 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
1987 if(this.debug === true)
1988 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
1991 this.colors.push(this.colors_assigned[label]);
1992 return this.colors_assigned[label];
1995 this.chartColors = function() {
1996 if(this.colors !== null) return this.colors;
1998 this.colors = new Array();
1999 this.colors_available = new Array();
2002 var c = $(this.element).data('colors');
2003 // this.log('read colors: ' + c);
2004 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2005 if(typeof c !== 'string') {
2006 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2013 for(i = 0, len = c.length; i < len ; i++) {
2015 this.colors_available.push(c[i]);
2016 // this.log('adding color: ' + c[i]);
2022 // push all the standard colors too
2023 for(i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2024 this.colors_available.push(NETDATA.themes.current.colors[i]);
2029 this.legendUpdateDOM = function() {
2032 // check that the legend DOM is up to date for the downloaded dimensions
2033 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2034 // this.log('the legend does not have any series - requesting legend update');
2037 else if(this.data === null) {
2038 // this.log('the chart does not have any data - requesting legend update');
2041 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2045 var labels = this.data.dimension_names.toString();
2046 if(labels !== this.element_legend_childs.series.labels_key) {
2049 if(this.debug === true)
2050 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2054 if(needed === false) {
2055 // make sure colors available
2058 // do we have to update the current values?
2059 // we do this, only when the visible chart is current
2060 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2061 if(this.debug === true)
2062 this.log('chart is in latest position... updating values on legend...');
2064 //var labels = this.data.dimension_names;
2065 //var i = labels.length;
2067 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2071 if(this.colors === null) {
2072 // this is the first time we update the chart
2073 // let's assign colors to all dimensions
2074 if(this.library.track_colors() === true)
2075 for(var dim in this.chart.dimensions)
2076 this._chartDimensionColor(this.chart.dimensions[dim].name);
2078 // we will re-generate the colors for the chart
2079 // based on the selected dimensions
2082 if(this.debug === true)
2083 this.log('updating Legend DOM');
2085 // mark all dimensions as invalid
2086 this.dimensions_visibility.invalidateAll();
2088 var genLabel = function(state, parent, name, count) {
2089 var color = state._chartDimensionColor(name);
2091 var user_element = null;
2092 var user_id = self.data('show-value-of-' + name + '-at') || null;
2093 if(user_id !== null) {
2094 user_element = document.getElementById(user_id) || null;
2095 if(user_element === null)
2096 state.log('Cannot find element with id: ' + user_id);
2099 state.element_legend_childs.series[name] = {
2100 name: document.createElement('span'),
2101 value: document.createElement('span'),
2106 var label = state.element_legend_childs.series[name];
2108 // create the dimension visibility tracking for this label
2109 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2111 var rgb = NETDATA.colorHex2Rgb(color);
2112 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2113 + state.chart.chart_type
2114 + '" style="background-color: '
2115 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2116 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2118 var text = document.createTextNode(' ' + name);
2119 label.name.appendChild(text);
2122 parent.appendChild(document.createElement('br'));
2124 parent.appendChild(label.name);
2125 parent.appendChild(label.value);
2128 var content = document.createElement('div');
2130 if(this.hasLegend()) {
2131 this.element_legend_childs = {
2133 resize_handler: document.createElement('div'),
2134 toolbox: document.createElement('div'),
2135 toolbox_left: document.createElement('div'),
2136 toolbox_right: document.createElement('div'),
2137 toolbox_reset: document.createElement('div'),
2138 toolbox_zoomin: document.createElement('div'),
2139 toolbox_zoomout: document.createElement('div'),
2140 toolbox_volume: document.createElement('div'),
2141 title_date: document.createElement('span'),
2142 title_time: document.createElement('span'),
2143 title_units: document.createElement('span'),
2144 nano: document.createElement('div'),
2146 paneClass: 'netdata-legend-series-pane',
2147 sliderClass: 'netdata-legend-series-slider',
2148 contentClass: 'netdata-legend-series-content',
2149 enabledClass: '__enabled',
2150 flashedClass: '__flashed',
2151 activeClass: '__active',
2153 alwaysVisible: true,
2159 this.element_legend.innerHTML = '';
2161 if(this.library.toolboxPanAndZoom !== null) {
2163 function get_pan_and_zoom_step(event) {
2165 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2167 else if (event.shiftKey)
2168 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2170 else if (event.altKey)
2171 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2174 return NETDATA.options.current.pan_and_zoom_factor;
2177 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2178 this.element.appendChild(this.element_legend_childs.toolbox);
2180 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2181 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2182 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2183 this.element_legend_childs.toolbox_left.onclick = function(e) {
2186 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2187 var before = that.view_before - step;
2188 var after = that.view_after - step;
2189 if(after >= that.netdata_first)
2190 that.library.toolboxPanAndZoom(that, after, before);
2192 if(NETDATA.options.current.show_help === true)
2193 $(this.element_legend_childs.toolbox_left).popover({
2198 placement: 'bottom',
2199 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2201 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>'
2205 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2206 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2207 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2208 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2210 NETDATA.resetAllCharts(that);
2212 if(NETDATA.options.current.show_help === true)
2213 $(this.element_legend_childs.toolbox_reset).popover({
2218 placement: 'bottom',
2219 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2220 title: 'Chart Reset',
2221 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>'
2224 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2225 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2226 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2227 this.element_legend_childs.toolbox_right.onclick = function(e) {
2229 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2230 var before = that.view_before + step;
2231 var after = that.view_after + step;
2232 if(before <= that.netdata_last)
2233 that.library.toolboxPanAndZoom(that, after, before);
2235 if(NETDATA.options.current.show_help === true)
2236 $(this.element_legend_childs.toolbox_right).popover({
2241 placement: 'bottom',
2242 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2244 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>'
2248 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2249 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2250 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2251 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2253 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2254 var before = that.view_before - dt;
2255 var after = that.view_after + dt;
2256 that.library.toolboxPanAndZoom(that, after, before);
2258 if(NETDATA.options.current.show_help === true)
2259 $(this.element_legend_childs.toolbox_zoomin).popover({
2264 placement: 'bottom',
2265 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2266 title: 'Chart Zoom In',
2267 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>'
2270 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2271 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2272 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2273 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2275 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);
2276 var before = that.view_before + dt;
2277 var after = that.view_after - dt;
2279 that.library.toolboxPanAndZoom(that, after, before);
2281 if(NETDATA.options.current.show_help === true)
2282 $(this.element_legend_childs.toolbox_zoomout).popover({
2287 placement: 'bottom',
2288 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2289 title: 'Chart Zoom Out',
2290 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>'
2293 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2294 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2295 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2296 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2297 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2298 //e.preventDefault();
2299 //alert('clicked toolbox_volume on ' + that.id);
2303 this.element_legend_childs.toolbox = null;
2304 this.element_legend_childs.toolbox_left = null;
2305 this.element_legend_childs.toolbox_reset = null;
2306 this.element_legend_childs.toolbox_right = null;
2307 this.element_legend_childs.toolbox_zoomin = null;
2308 this.element_legend_childs.toolbox_zoomout = null;
2309 this.element_legend_childs.toolbox_volume = null;
2312 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2313 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2314 this.element.appendChild(this.element_legend_childs.resize_handler);
2315 if(NETDATA.options.current.show_help === true)
2316 $(this.element_legend_childs.resize_handler).popover({
2321 placement: 'bottom',
2322 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2323 title: 'Chart Resize',
2324 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>'
2328 this.element_legend_childs.resize_handler.onmousedown =
2330 that.resizeHandler(e);
2334 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2335 that.resizeHandler(e);
2338 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2339 this.element_legend.appendChild(this.element_legend_childs.title_date);
2341 this.element_legend.appendChild(document.createElement('br'));
2343 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2344 this.element_legend.appendChild(this.element_legend_childs.title_time);
2346 this.element_legend.appendChild(document.createElement('br'));
2348 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2349 this.element_legend.appendChild(this.element_legend_childs.title_units);
2351 this.element_legend.appendChild(document.createElement('br'));
2353 this.element_legend_childs.nano.className = 'netdata-legend-series';
2354 this.element_legend.appendChild(this.element_legend_childs.nano);
2356 content.className = 'netdata-legend-series-content';
2357 this.element_legend_childs.nano.appendChild(content);
2359 if(NETDATA.options.current.show_help === true)
2360 $(content).popover({
2365 placement: 'bottom',
2366 title: 'Chart Legend',
2367 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2368 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>'
2372 this.element_legend_childs = {
2374 resize_handler: null,
2377 toolbox_right: null,
2378 toolbox_reset: null,
2379 toolbox_zoomin: null,
2380 toolbox_zoomout: null,
2381 toolbox_volume: null,
2392 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2393 if(this.debug === true)
2394 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2396 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2397 genLabel(this, content, this.data.dimension_names[i], i);
2401 var tmp = new Array();
2402 for(var dim in this.chart.dimensions) {
2403 tmp.push(this.chart.dimensions[dim].name);
2404 genLabel(this, content, this.chart.dimensions[dim].name, i);
2406 this.element_legend_childs.series.labels_key = tmp.toString();
2407 if(this.debug === true)
2408 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2411 // create a hidden div to be used for hidding
2412 // the original legend of the chart library
2413 var el = document.createElement('div');
2414 if(this.element_legend !== null)
2415 this.element_legend.appendChild(el);
2416 el.style.display = 'none';
2418 this.element_legend_childs.hidden = document.createElement('div');
2419 el.appendChild(this.element_legend_childs.hidden);
2421 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2422 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2424 this.legendShowLatestValues();
2427 this.hasLegend = function() {
2428 if(typeof this.___hasLegendCache___ !== 'undefined')
2429 return this.___hasLegendCache___;
2432 if(this.library && this.library.legend(this) === 'right-side') {
2433 var legend = $(this.element).data('legend') || 'yes';
2434 if(legend === 'yes') leg = true;
2437 this.___hasLegendCache___ = leg;
2441 this.legendWidth = function() {
2442 return (this.hasLegend())?140:0;
2445 this.legendHeight = function() {
2446 return $(this.element).height();
2449 this.chartWidth = function() {
2450 return $(this.element).width() - this.legendWidth();
2453 this.chartHeight = function() {
2454 return $(this.element).height();
2457 this.chartPixelsPerPoint = function() {
2458 // force an options provided detail
2459 var px = this.pixels_per_point;
2461 if(this.library && px < this.library.pixels_per_point(this))
2462 px = this.library.pixels_per_point(this);
2464 if(px < NETDATA.options.current.pixels_per_point)
2465 px = NETDATA.options.current.pixels_per_point;
2470 this.needsRecreation = function() {
2472 this.chart_created === true
2474 && this.library.autoresize() === false
2475 && this.tm.last_resized < NETDATA.options.last_resized
2479 this.chartURL = function() {
2480 var after, before, points_multiplier = 1;
2481 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2482 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2484 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2485 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2486 this.view_after = after * 1000;
2487 this.view_before = before * 1000;
2489 this.requested_padding = null;
2490 points_multiplier = 1;
2492 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2493 this.tm.pan_and_zoom_seq = 0;
2495 before = Math.round(this.current.force_before_ms / 1000);
2496 after = Math.round(this.current.force_after_ms / 1000);
2497 this.view_after = after * 1000;
2498 this.view_before = before * 1000;
2500 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2501 this.requested_padding = Math.round((before - after) / 2);
2502 after -= this.requested_padding;
2503 before += this.requested_padding;
2504 this.requested_padding *= 1000;
2505 points_multiplier = 2;
2508 this.current.force_before_ms = null;
2509 this.current.force_after_ms = null;
2512 this.tm.pan_and_zoom_seq = 0;
2514 before = this.before;
2516 this.view_after = after * 1000;
2517 this.view_before = before * 1000;
2519 this.requested_padding = null;
2520 points_multiplier = 1;
2523 this.requested_after = after * 1000;
2524 this.requested_before = before * 1000;
2526 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2528 // build the data URL
2529 this.data_url = this.host + this.chart.data_url;
2530 this.data_url += "&format=" + this.library.format();
2531 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2532 this.data_url += "&group=" + this.method;
2533 this.data_url += "&options=" + this.library.options(this);
2534 this.data_url += '|jsonwrap';
2536 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2537 this.data_url += '|nonzero';
2539 if(this.append_options !== null)
2540 this.data_url += '|' + this.append_options.toString();
2543 this.data_url += "&after=" + after.toString();
2546 this.data_url += "&before=" + before.toString();
2549 this.data_url += "&dimensions=" + this.dimensions;
2551 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2552 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2555 this.redrawChart = function() {
2556 if(this.data !== null)
2557 this.updateChartWithData(this.data);
2560 this.updateChartWithData = function(data) {
2561 if(this.debug === true)
2562 this.log('updateChartWithData() called.');
2564 this._updating = false;
2566 // this may force the chart to be re-created
2570 this.updates_counter++;
2571 this.updates_since_last_unhide++;
2572 this.updates_since_last_creation++;
2574 var started = new Date().getTime();
2576 // if the result is JSON, find the latest update-every
2577 this.data_update_every = data.view_update_every * 1000;
2578 this.data_after = data.after * 1000;
2579 this.data_before = data.before * 1000;
2580 this.netdata_first = data.first_entry * 1000;
2581 this.netdata_last = data.last_entry * 1000;
2582 this.data_points = data.points;
2585 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2586 if(this.view_after < this.data_after) {
2587 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2588 this.view_after = this.data_after;
2591 if(this.view_before > this.data_before) {
2592 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2593 this.view_before = this.data_before;
2597 this.view_after = this.data_after;
2598 this.view_before = this.data_before;
2601 if(this.debug === true) {
2602 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2604 if(this.current.force_after_ms)
2605 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2607 this.log('STATUS: forced : unset');
2609 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2610 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2611 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2612 this.log('STATUS: points : ' + (this.data_points).toString());
2615 if(this.data_points === 0) {
2620 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2621 if(this.debug === true)
2622 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2624 this.chart_created = false;
2627 // check and update the legend
2628 this.legendUpdateDOM();
2630 if(this.chart_created === true
2631 && typeof this.library.update === 'function') {
2633 if(this.debug === true)
2634 this.log('updating chart...');
2636 if(callChartLibraryUpdateSafely(data) === false)
2640 if(this.debug === true)
2641 this.log('creating chart...');
2643 if(callChartLibraryCreateSafely(data) === false)
2647 this.legendShowLatestValues();
2648 if(this.selected === true)
2649 NETDATA.globalSelectionSync.stop();
2651 // update the performance counters
2652 var now = new Date().getTime();
2653 this.tm.last_updated = now;
2655 // don't update last_autorefreshed if this chart is
2656 // forced to be updated with global PanAndZoom
2657 if(NETDATA.globalPanAndZoom.isActive())
2658 this.tm.last_autorefreshed = 0;
2660 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes)
2661 this.tm.last_autorefreshed = Math.round(now / this.data_update_every) * this.data_update_every;
2663 this.tm.last_autorefreshed = now;
2666 this.refresh_dt_ms = now - started;
2667 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2669 if(this.refresh_dt_element !== null)
2670 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2673 this.updateChart = function(callback) {
2674 if(this.debug === true)
2675 this.log('updateChart() called.');
2677 if(this._updating === true) {
2678 if(this.debug === true)
2679 this.log('I am already updating...');
2681 if(typeof callback === 'function') callback();
2685 // due to late initialization of charts and libraries
2686 // we need to check this too
2687 if(this.enabled === false) {
2688 if(this.debug === true)
2689 this.log('I am not enabled');
2691 if(typeof callback === 'function') callback();
2695 if(canBeRendered() === false) {
2696 if(typeof callback === 'function') callback();
2700 if(this.chart === null) {
2701 this.getChart(function() { that.updateChart(callback); });
2705 if(this.library.initialized === false) {
2706 if(this.library.enabled === true) {
2707 this.library.initialize(function() { that.updateChart(callback); });
2711 error('chart library "' + this.library_name + '" is not available.');
2712 if(typeof callback === 'function') callback();
2717 this.clearSelection();
2720 if(this.debug === true)
2721 this.log('updating from ' + this.data_url);
2723 this._updating = true;
2725 this.xhr = $.ajax( {
2727 crossDomain: NETDATA.options.crossDomainAjax,
2731 .success(function(data) {
2732 if(that.debug === true)
2733 that.log('data received. updating chart.');
2735 that.updateChartWithData(data);
2738 error('data download failed for url: ' + that.data_url);
2740 .always(function() {
2741 that._updating = false;
2742 if(typeof callback === 'function') callback();
2748 this.isVisible = function(nocache) {
2749 if(typeof nocache === 'undefined')
2752 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2754 // caching - we do not evaluate the charts visibility
2755 // if the page has not been scrolled since the last check
2756 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2757 return this.___isVisible___;
2759 this.tm.last_visible_check = new Date().getTime();
2761 var wh = window.innerHeight;
2762 var x = this.element.getBoundingClientRect();
2766 if(x.width === 0 || x.height === 0) {
2768 this.___isVisible___ = false;
2769 return this.___isVisible___;
2772 if(x.top < 0 && -x.top > x.height) {
2773 // the chart is entirely above
2774 ret = -x.top - x.height;
2776 else if(x.top > wh) {
2777 // the chart is entirely below
2781 if(ret > tolerance) {
2782 // the chart is too far
2785 this.___isVisible___ = false;
2786 return this.___isVisible___;
2789 // the chart is inside or very close
2792 this.___isVisible___ = true;
2793 return this.___isVisible___;
2797 this.isAutoRefreshed = function() {
2798 return (this.current.autorefresh);
2801 this.canBeAutoRefreshed = function() {
2802 var now = new Date().getTime();
2804 if(this.enabled === false) {
2805 if(this.debug === true)
2806 this.log('I am not enabled');
2811 if(this.library === null || this.library.enabled === false) {
2812 error('charting library "' + this.library_name + '" is not available');
2813 if(this.debug === true)
2814 this.log('My chart library ' + this.library_name + ' is not available');
2819 if(this.isVisible() === false) {
2820 if(NETDATA.options.debug.visibility === true || this.debug === true)
2821 this.log('I am not visible');
2826 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2827 if(this.debug === true)
2828 this.log('timed force update detected - allowing this update');
2830 this.current.force_update_at = 0;
2834 if(this.isAutoRefreshed() === true) {
2835 // allow the first update, even if the page is not visible
2836 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2837 if(NETDATA.options.debug.focus === true || this.debug === true)
2838 this.log('canBeAutoRefreshed(): page does not have focus');
2843 if(this.needsRecreation() === true) {
2844 if(this.debug === true)
2845 this.log('canBeAutoRefreshed(): needs re-creation.');
2850 // options valid only for autoRefresh()
2851 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
2852 if(NETDATA.globalPanAndZoom.isActive()) {
2853 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
2854 if(this.debug === true)
2855 this.log('canBeAutoRefreshed(): global panning: I need an update.');
2860 if(this.debug === true)
2861 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
2867 if(this.selected === true) {
2868 if(this.debug === true)
2869 this.log('canBeAutoRefreshed(): I have a selection in place.');
2874 if(this.paused === true) {
2875 if(this.debug === true)
2876 this.log('canBeAutoRefreshed(): I am paused.');
2881 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
2882 if(this.debug === true)
2883 this.log('canBeAutoRefreshed(): It is time to update me.');
2893 this.autoRefresh = function(callback) {
2894 if(this.canBeAutoRefreshed() === true) {
2895 this.updateChart(callback);
2898 if(typeof callback !== 'undefined')
2903 this._defaultsFromDownloadedChart = function(chart) {
2905 this.chart_url = chart.url;
2906 this.data_update_every = chart.update_every * 1000;
2907 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2908 this.tm.last_info_downloaded = new Date().getTime();
2910 if(this.title === null)
2911 this.title = chart.title;
2913 if(this.units === null)
2914 this.units = chart.units;
2917 // fetch the chart description from the netdata server
2918 this.getChart = function(callback) {
2919 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
2921 this._defaultsFromDownloadedChart(this.chart);
2922 if(typeof callback === 'function') callback();
2925 this.chart_url = "/api/v1/chart?chart=" + this.id;
2927 if(this.debug === true)
2928 this.log('downloading ' + this.chart_url);
2931 url: this.host + this.chart_url,
2932 crossDomain: NETDATA.options.crossDomainAjax,
2936 .done(function(chart) {
2937 chart.url = that.chart_url;
2938 that._defaultsFromDownloadedChart(chart);
2939 NETDATA.chartRegistry.add(that.host, that.id, chart);
2942 NETDATA.error(404, that.chart_url);
2943 error('chart not found on url "' + that.chart_url + '"');
2945 .always(function() {
2946 if(typeof callback === 'function') callback();
2951 // ============================================================================================================
2957 NETDATA.resetAllCharts = function(state) {
2958 // first clear the global selection sync
2959 // to make sure no chart is in selected state
2960 state.globalSelectionSyncStop();
2962 // there are 2 possibilities here
2963 // a. state is the global Pan and Zoom master
2964 // b. state is not the global Pan and Zoom master
2966 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
2969 // clear the global Pan and Zoom
2970 // this will also refresh the master
2971 // and unblock any charts currently mirroring the master
2972 NETDATA.globalPanAndZoom.clearMaster();
2974 // if we were not the master, reset our status too
2975 // this is required because most probably the mouse
2976 // is over this chart, blocking it from auto-refreshing
2977 if(master === false && (state.paused === true || state.selected === true))
2981 // get or create a chart state, given a DOM element
2982 NETDATA.chartState = function(element) {
2983 var state = $(element).data('netdata-state-object') || null;
2984 if(state === null) {
2985 state = new chartState(element);
2986 $(element).data('netdata-state-object', state);
2991 // ----------------------------------------------------------------------------------------------------------------
2992 // Library functions
2994 // Load a script without jquery
2995 // This is used to load jquery - after it is loaded, we use jquery
2996 NETDATA._loadjQuery = function(callback) {
2997 if(typeof jQuery === 'undefined') {
2998 if(NETDATA.options.debug.main_loop === true)
2999 console.log('loading ' + NETDATA.jQuery);
3001 var script = document.createElement('script');
3002 script.type = 'text/javascript';
3003 script.async = true;
3004 script.src = NETDATA.jQuery;
3006 // script.onabort = onError;
3007 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3008 if(typeof callback === "function")
3009 script.onload = callback;
3011 var s = document.getElementsByTagName('script')[0];
3012 s.parentNode.insertBefore(script, s);
3014 else if(typeof callback === "function")
3018 NETDATA._loadCSS = function(filename) {
3019 // don't use jQuery here
3020 // styles are loaded before jQuery
3021 // to eliminate showing an unstyled page to the user
3023 var fileref = document.createElement("link");
3024 fileref.setAttribute("rel", "stylesheet");
3025 fileref.setAttribute("type", "text/css");
3026 fileref.setAttribute("href", filename);
3028 if (typeof fileref !== 'undefined')
3029 document.getElementsByTagName("head")[0].appendChild(fileref);
3032 NETDATA.colorHex2Rgb = function(hex) {
3033 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3034 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3035 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3036 return r + r + g + g + b + b;
3039 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3041 r: parseInt(result[1], 16),
3042 g: parseInt(result[2], 16),
3043 b: parseInt(result[3], 16)
3047 NETDATA.colorLuminance = function(hex, lum) {
3048 // validate hex string
3049 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3051 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3055 // convert to decimal and change luminosity
3056 var rgb = "#", c, i;
3057 for (i = 0; i < 3; i++) {
3058 c = parseInt(hex.substr(i*2,2), 16);
3059 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3060 rgb += ("00"+c).substr(c.length);
3066 NETDATA.guid = function() {
3068 return Math.floor((1 + Math.random()) * 0x10000)
3073 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3076 NETDATA.zeropad = function(x) {
3077 if(x > -10 && x < 10) return '0' + x.toString();
3078 else return x.toString();
3081 // user function to signal us the DOM has been
3083 NETDATA.updatedDom = function() {
3084 NETDATA.options.updated_dom = true;
3087 NETDATA.ready = function(callback) {
3088 NETDATA.options.pauseCallback = callback;
3091 NETDATA.pause = function(callback) {
3092 if(NETDATA.options.pause === true)
3095 NETDATA.options.pauseCallback = callback;
3098 NETDATA.unpause = function() {
3099 NETDATA.options.pauseCallback = null;
3100 NETDATA.options.updated_dom = true;
3101 NETDATA.options.pause = false;
3104 // ----------------------------------------------------------------------------------------------------------------
3106 // this is purely sequencial charts refresher
3107 // it is meant to be autonomous
3108 NETDATA.chartRefresherNoParallel = function(index) {
3109 if(NETDATA.options.debug.mail_loop === true)
3110 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3112 if(NETDATA.options.updated_dom === true) {
3113 // the dom has been updated
3114 // get the dom parts again
3115 NETDATA.parseDom(NETDATA.chartRefresher);
3118 if(index >= NETDATA.options.targets.length) {
3119 if(NETDATA.options.debug.main_loop === true)
3120 console.log('waiting to restart main loop...');
3122 NETDATA.options.auto_refresher_fast_weight = 0;
3124 setTimeout(function() {
3125 NETDATA.chartRefresher();
3126 }, NETDATA.options.current.idle_between_loops);
3129 var state = NETDATA.options.targets[index];
3131 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3132 if(NETDATA.options.debug.main_loop === true)
3133 console.log('fast rendering...');
3135 state.autoRefresh(function() {
3136 NETDATA.chartRefresherNoParallel(++index);
3140 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3141 NETDATA.options.auto_refresher_fast_weight = 0;
3143 setTimeout(function() {
3144 state.autoRefresh(function() {
3145 NETDATA.chartRefresherNoParallel(++index);
3147 }, NETDATA.options.current.idle_between_charts);
3152 // this is part of the parallel refresher
3153 // its cause is to refresh sequencially all the charts
3154 // that depend on chart library initialization
3155 // it will call the parallel refresher back
3156 // as soon as it sees a chart that its chart library
3158 NETDATA.chartRefresher_uninitialized = function() {
3159 if(NETDATA.options.updated_dom === true) {
3160 // the dom has been updated
3161 // get the dom parts again
3162 NETDATA.parseDom(NETDATA.chartRefresher);
3166 if(NETDATA.options.sequencial.length === 0)
3167 NETDATA.chartRefresher();
3169 var state = NETDATA.options.sequencial.pop();
3170 if(state.library.initialized === true)
3171 NETDATA.chartRefresher();
3173 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3177 NETDATA.chartRefresherWaitTime = function() {
3178 return NETDATA.options.current.idle_parallel_loops;
3181 // the default refresher
3182 // it will create 2 sets of charts:
3183 // - the ones that can be refreshed in parallel
3184 // - the ones that depend on something else
3185 // the first set will be executed in parallel
3186 // the second will be given to NETDATA.chartRefresher_uninitialized()
3187 NETDATA.chartRefresher = function() {
3188 if(NETDATA.options.pause === true) {
3189 // console.log('auto-refresher is paused');
3190 setTimeout(NETDATA.chartRefresher,
3191 NETDATA.chartRefresherWaitTime());
3195 if(typeof NETDATA.options.pauseCallback === 'function') {
3196 // console.log('auto-refresher is calling pauseCallback');
3197 NETDATA.options.pause = true;
3198 NETDATA.options.pauseCallback();
3199 NETDATA.chartRefresher();
3203 if(NETDATA.options.current.parallel_refresher === false) {
3204 NETDATA.chartRefresherNoParallel(0);
3208 if(NETDATA.options.updated_dom === true) {
3209 // the dom has been updated
3210 // get the dom parts again
3211 NETDATA.parseDom(NETDATA.chartRefresher);
3215 var parallel = new Array();
3216 var targets = NETDATA.options.targets;
3217 var len = targets.length;
3219 if(targets[len].isVisible() === false)
3222 var state = targets[len];
3223 if(state.library.initialized === false) {
3224 if(state.library.enabled === true) {
3225 state.library.initialize(NETDATA.chartRefresher);
3229 state.error('chart library "' + state.library_name + '" is not enabled.');
3233 parallel.unshift(state);
3236 if(parallel.length > 0) {
3237 var parallel_jobs = parallel.length;
3239 // this will execute the jobs in parallel
3240 $(parallel).each(function() {
3241 this.autoRefresh(function() {
3244 if(parallel_jobs === 0) {
3245 setTimeout(NETDATA.chartRefresher,
3246 NETDATA.chartRefresherWaitTime());
3252 setTimeout(NETDATA.chartRefresher,
3253 NETDATA.chartRefresherWaitTime());
3257 NETDATA.parseDom = function(callback) {
3258 NETDATA.options.last_page_scroll = new Date().getTime();
3259 NETDATA.options.updated_dom = false;
3261 var targets = $('div[data-netdata]'); //.filter(':visible');
3263 if(NETDATA.options.debug.main_loop === true)
3264 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3266 NETDATA.options.targets = new Array();
3267 var len = targets.length;
3269 // the initialization will take care of sizing
3270 // and the "loading..." message
3271 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3274 if(typeof callback === 'function') callback();
3277 // this is the main function - where everything starts
3278 NETDATA.start = function() {
3279 // this should be called only once
3281 NETDATA.options.page_is_visible = true;
3283 $(window).blur(function() {
3284 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3285 NETDATA.options.page_is_visible = false;
3286 if(NETDATA.options.debug.focus === true)
3287 console.log('Lost Focus!');
3291 $(window).focus(function() {
3292 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3293 NETDATA.options.page_is_visible = true;
3294 if(NETDATA.options.debug.focus === true)
3295 console.log('Focus restored!');
3299 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3300 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3301 NETDATA.options.page_is_visible = false;
3302 if(NETDATA.options.debug.focus === true)
3303 console.log('Document has no focus!');
3307 // bootstrap tab switching
3308 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3310 // bootstrap modal switching
3311 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3312 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3314 NETDATA.parseDom(NETDATA.chartRefresher);
3317 // ----------------------------------------------------------------------------------------------------------------
3320 NETDATA.peityInitialize = function(callback) {
3321 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3323 url: NETDATA.peity_js,
3328 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3331 NETDATA.chartLibraries.peity.enabled = false;
3332 NETDATA.error(100, NETDATA.peity_js);
3334 .always(function() {
3335 if(typeof callback === "function")
3340 NETDATA.chartLibraries.peity.enabled = false;
3341 if(typeof callback === "function")
3346 NETDATA.peityChartUpdate = function(state, data) {
3347 state.peity_instance.innerHTML = data.result;
3349 if(state.peity_options.stroke !== state.chartColors()[0]) {
3350 state.peity_options.stroke = state.chartColors()[0];
3351 if(state.chart.chart_type === 'line')
3352 state.peity_options.fill = NETDATA.themes.current.background;
3354 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3357 $(state.peity_instance).peity('line', state.peity_options);
3361 NETDATA.peityChartCreate = function(state, data) {
3362 state.peity_instance = document.createElement('div');
3363 state.element_chart.appendChild(state.peity_instance);
3365 var self = $(state.element);
3366 state.peity_options = {
3367 stroke: NETDATA.themes.current.foreground,
3368 strokeWidth: self.data('peity-strokewidth') || 1,
3369 width: state.chartWidth(),
3370 height: state.chartHeight(),
3371 fill: NETDATA.themes.current.foreground
3374 NETDATA.peityChartUpdate(state, data);
3378 // ----------------------------------------------------------------------------------------------------------------
3381 NETDATA.sparklineInitialize = function(callback) {
3382 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3384 url: NETDATA.sparkline_js,
3389 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3392 NETDATA.chartLibraries.sparkline.enabled = false;
3393 NETDATA.error(100, NETDATA.sparkline_js);
3395 .always(function() {
3396 if(typeof callback === "function")
3401 NETDATA.chartLibraries.sparkline.enabled = false;
3402 if(typeof callback === "function")
3407 NETDATA.sparklineChartUpdate = function(state, data) {
3408 state.sparkline_options.width = state.chartWidth();
3409 state.sparkline_options.height = state.chartHeight();
3411 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3415 NETDATA.sparklineChartCreate = function(state, data) {
3416 var self = $(state.element);
3417 var type = self.data('sparkline-type') || 'line';
3418 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3419 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3420 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3421 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3422 var composite = self.data('sparkline-composite') || undefined;
3423 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3424 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3425 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3426 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3427 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3428 var spotColor = self.data('sparkline-spotcolor') || undefined;
3429 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3430 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3431 var spotRadius = self.data('sparkline-spotradius') || undefined;
3432 var valueSpots = self.data('sparkline-valuespots') || undefined;
3433 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3434 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3435 var lineWidth = self.data('sparkline-linewidth') || undefined;
3436 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3437 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3438 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3439 var xvalues = self.data('sparkline-xvalues') || undefined;
3440 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3441 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3442 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3443 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3444 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3445 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3446 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3447 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3448 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3449 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3450 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3451 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3452 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3453 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3454 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3455 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3456 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3457 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3458 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3459 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3460 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3461 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3463 state.sparkline_options = {
3465 lineColor: lineColor,
3466 fillColor: fillColor,
3467 chartRangeMin: chartRangeMin,
3468 chartRangeMax: chartRangeMax,
3469 composite: composite,
3470 enableTagOptions: enableTagOptions,
3471 tagOptionPrefix: tagOptionPrefix,
3472 tagValuesAttribute: tagValuesAttribute,
3473 disableHiddenCheck: disableHiddenCheck,
3474 defaultPixelsPerValue: defaultPixelsPerValue,
3475 spotColor: spotColor,
3476 minSpotColor: minSpotColor,
3477 maxSpotColor: maxSpotColor,
3478 spotRadius: spotRadius,
3479 valueSpots: valueSpots,
3480 highlightSpotColor: highlightSpotColor,
3481 highlightLineColor: highlightLineColor,
3482 lineWidth: lineWidth,
3483 normalRangeMin: normalRangeMin,
3484 normalRangeMax: normalRangeMax,
3485 drawNormalOnTop: drawNormalOnTop,
3487 chartRangeClip: chartRangeClip,
3488 chartRangeMinX: chartRangeMinX,
3489 chartRangeMaxX: chartRangeMaxX,
3490 disableInteraction: disableInteraction,
3491 disableTooltips: disableTooltips,
3492 disableHighlight: disableHighlight,
3493 highlightLighten: highlightLighten,
3494 highlightColor: highlightColor,
3495 tooltipContainer: tooltipContainer,
3496 tooltipClassname: tooltipClassname,
3497 tooltipChartTitle: state.title,
3498 tooltipFormat: tooltipFormat,
3499 tooltipPrefix: tooltipPrefix,
3500 tooltipSuffix: tooltipSuffix,
3501 tooltipSkipNull: tooltipSkipNull,
3502 tooltipValueLookups: tooltipValueLookups,
3503 tooltipFormatFieldlist: tooltipFormatFieldlist,
3504 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3505 numberFormatter: numberFormatter,
3506 numberDigitGroupSep: numberDigitGroupSep,
3507 numberDecimalMark: numberDecimalMark,
3508 numberDigitGroupCount: numberDigitGroupCount,
3509 animatedZooms: animatedZooms,
3510 width: state.chartWidth(),
3511 height: state.chartHeight()
3514 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3518 // ----------------------------------------------------------------------------------------------------------------
3525 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3526 if(after < state.netdata_first)
3527 after = state.netdata_first;
3529 if(before > state.netdata_last)
3530 before = state.netdata_last;
3532 state.setMode('zoom');
3533 state.globalSelectionSyncStop();
3534 state.globalSelectionSyncDelay();
3535 state.dygraph_user_action = true;
3536 state.dygraph_force_zoom = true;
3537 state.updateChartPanOrZoom(after, before);
3538 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3541 NETDATA.dygraphSetSelection = function(state, t) {
3542 if(typeof state.dygraph_instance !== 'undefined') {
3543 var r = state.calculateRowForTime(t);
3545 state.dygraph_instance.setSelection(r);
3547 state.dygraph_instance.clearSelection();
3548 state.legendShowUndefined();
3555 NETDATA.dygraphClearSelection = function(state, t) {
3556 if(typeof state.dygraph_instance !== 'undefined') {
3557 state.dygraph_instance.clearSelection();
3562 NETDATA.dygraphSmoothInitialize = function(callback) {
3564 url: NETDATA.dygraph_smooth_js,
3569 NETDATA.dygraph.smooth = true;
3570 smoothPlotter.smoothing = 0.3;
3573 NETDATA.dygraph.smooth = false;
3575 .always(function() {
3576 if(typeof callback === "function")
3581 NETDATA.dygraphInitialize = function(callback) {
3582 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3584 url: NETDATA.dygraph_js,
3589 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3592 NETDATA.chartLibraries.dygraph.enabled = false;
3593 NETDATA.error(100, NETDATA.dygraph_js);
3595 .always(function() {
3596 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3597 NETDATA.dygraphSmoothInitialize(callback);
3598 else if(typeof callback === "function")
3603 NETDATA.chartLibraries.dygraph.enabled = false;
3604 if(typeof callback === "function")
3609 NETDATA.dygraphChartUpdate = function(state, data) {
3610 var dygraph = state.dygraph_instance;
3612 if(typeof dygraph === 'undefined')
3613 return NETDATA.dygraphChartCreate(state, data);
3615 // when the chart is not visible, and hidden
3616 // if there is a window resize, dygraph detects
3617 // its element size as 0x0.
3618 // this will make it re-appear properly
3620 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3624 file: data.result.data,
3625 colors: state.chartColors(),
3626 labels: data.result.labels,
3627 labelsDivWidth: state.chartWidth() - 70,
3628 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3631 if(state.dygraph_force_zoom === true) {
3632 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3633 state.log('dygraphChartUpdate() forced zoom update');
3635 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3636 options.valueRange = null;
3637 options.isZoomedIgnoreProgrammaticZoom = true;
3638 state.dygraph_force_zoom = false;
3640 else if(state.current.name !== 'auto') {
3641 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3642 state.log('dygraphChartUpdate() loose update');
3645 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3646 state.log('dygraphChartUpdate() strict update');
3648 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3649 options.valueRange = null;
3650 options.isZoomedIgnoreProgrammaticZoom = true;
3653 if(state.dygraph_smooth_eligible === true) {
3654 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3655 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3656 NETDATA.dygraphChartCreate(state, data);
3661 dygraph.updateOptions(options);
3663 state.dygraph_last_rendered = new Date().getTime();
3667 NETDATA.dygraphChartCreate = function(state, data) {
3668 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3669 state.log('dygraphChartCreate()');
3671 var self = $(state.element);
3673 var chart_type = state.chart.chart_type;
3674 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3675 chart_type = self.data('dygraph-type') || chart_type;
3677 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3678 smooth = self.data('dygraph-smooth') || smooth;
3680 if(NETDATA.dygraph.smooth === false)
3683 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3684 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3686 state.dygraph_options = {
3687 colors: self.data('dygraph-colors') || state.chartColors(),
3689 // leave a few pixels empty on the right of the chart
3690 rightGap: self.data('dygraph-rightgap') || 5,
3691 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3692 showRoller: self.data('dygraph-showroller') || false,
3694 title: self.data('dygraph-title') || state.title,
3695 titleHeight: self.data('dygraph-titleheight') || 19,
3697 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3698 labels: data.result.labels,
3699 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3700 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3701 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3702 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3703 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3706 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3707 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3709 ylabel: state.units,
3710 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3712 // the function to plot the chart
3715 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3716 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3717 strokePattern: self.data('dygraph-strokepattern') || undefined,
3719 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3720 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3721 drawPoints: self.data('dygraph-drawpoints') || false,
3723 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3724 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3726 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3727 pointSize: self.data('dygraph-pointsize') || 1,
3729 // enabling this makes the chart with little square lines
3730 stepPlot: self.data('dygraph-stepplot') || false,
3732 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3733 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3734 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3736 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3737 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3738 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3739 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3741 drawAxis: self.data('dygraph-drawaxis') || true,
3742 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3743 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3744 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3746 drawGrid: self.data('dygraph-drawgrid') || true,
3747 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3748 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3749 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3750 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3751 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3753 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3754 sigFigs: self.data('dygraph-sigfigs') || null,
3755 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3756 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3758 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3759 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3760 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3762 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3763 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3767 ticker: Dygraph.dateTicker,
3768 axisLabelFormatter: function (d, gran) {
3769 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3771 valueFormatter: function (ms) {
3772 var d = new Date(ms);
3773 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3774 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3779 valueFormatter: function (x) {
3780 // we format legends with the state object
3781 // no need to do anything here
3782 // return (Math.round(x*100) / 100).toLocaleString();
3783 // return state.legendFormatValue(x);
3788 legendFormatter: function(data) {
3789 var elements = state.element_legend_childs;
3791 // if the hidden div is not there
3792 // we are not managing the legend
3793 if(elements.hidden === null) return;
3795 if (typeof data.x !== 'undefined') {
3796 state.legendSetDate(data.x);
3797 var i = data.series.length;
3799 var series = data.series[i];
3800 if(!series.isVisible) continue;
3801 state.legendSetLabelValue(series.label, series.y);
3807 drawCallback: function(dygraph, is_initial) {
3808 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3809 state.dygraph_user_action = false;
3811 var x_range = dygraph.xAxisRange();
3812 var after = Math.round(x_range[0]);
3813 var before = Math.round(x_range[1]);
3815 if(NETDATA.options.debug.dygraph === true)
3816 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3818 if(before <= state.netdata_last && after >= state.netdata_first)
3819 state.updateChartPanOrZoom(after, before);
3822 zoomCallback: function(minDate, maxDate, yRanges) {
3823 if(NETDATA.options.debug.dygraph === true)
3824 state.log('dygraphZoomCallback()');
3826 state.globalSelectionSyncStop();
3827 state.globalSelectionSyncDelay();
3828 state.setMode('zoom');
3830 // refresh it to the greatest possible zoom level
3831 state.dygraph_user_action = true;
3832 state.dygraph_force_zoom = true;
3833 state.updateChartPanOrZoom(minDate, maxDate);
3835 highlightCallback: function(event, x, points, row, seriesName) {
3836 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3837 state.log('dygraphHighlightCallback()');
3841 // there is a bug in dygraph when the chart is zoomed enough
3842 // the time it thinks is selected is wrong
3843 // here we calculate the time t based on the row number selected
3845 var t = state.data_after + row * state.data_update_every;
3846 // 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);
3848 state.globalSelectionSync(x);
3850 // fix legend zIndex using the internal structures of dygraph legend module
3851 // this works, but it is a hack!
3852 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
3854 unhighlightCallback: function(event) {
3855 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3856 state.log('dygraphUnhighlightCallback()');
3858 state.unpauseChart();
3859 state.globalSelectionSyncStop();
3861 interactionModel : {
3862 mousedown: function(event, dygraph, context) {
3863 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3864 state.log('interactionModel.mousedown()');
3866 state.dygraph_user_action = true;
3867 state.globalSelectionSyncStop();
3869 if(NETDATA.options.debug.dygraph === true)
3870 state.log('dygraphMouseDown()');
3872 // Right-click should not initiate a zoom.
3873 if(event.button && event.button === 2) return;
3875 context.initializeMouseDown(event, dygraph, context);
3877 if(event.button && event.button === 1) {
3878 if (event.altKey || event.shiftKey) {
3879 state.setMode('pan');
3880 state.globalSelectionSyncDelay();
3881 Dygraph.startPan(event, dygraph, context);
3884 state.setMode('zoom');
3885 state.globalSelectionSyncDelay();
3886 Dygraph.startZoom(event, dygraph, context);
3890 if (event.altKey || event.shiftKey) {
3891 state.setMode('zoom');
3892 state.globalSelectionSyncDelay();
3893 Dygraph.startZoom(event, dygraph, context);
3896 state.setMode('pan');
3897 state.globalSelectionSyncDelay();
3898 Dygraph.startPan(event, dygraph, context);
3902 mousemove: function(event, dygraph, context) {
3903 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3904 state.log('interactionModel.mousemove()');
3906 if(context.isPanning) {
3907 state.dygraph_user_action = true;
3908 state.globalSelectionSyncStop();
3909 state.globalSelectionSyncDelay();
3910 state.setMode('pan');
3911 Dygraph.movePan(event, dygraph, context);
3913 else if(context.isZooming) {
3914 state.dygraph_user_action = true;
3915 state.globalSelectionSyncStop();
3916 state.globalSelectionSyncDelay();
3917 state.setMode('zoom');
3918 Dygraph.moveZoom(event, dygraph, context);
3921 mouseup: function(event, dygraph, context) {
3922 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3923 state.log('interactionModel.mouseup()');
3925 if (context.isPanning) {
3926 state.dygraph_user_action = true;
3927 state.globalSelectionSyncDelay();
3928 Dygraph.endPan(event, dygraph, context);
3930 else if (context.isZooming) {
3931 state.dygraph_user_action = true;
3932 state.globalSelectionSyncDelay();
3933 Dygraph.endZoom(event, dygraph, context);
3936 click: function(event, dygraph, context) {
3937 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3938 state.log('interactionModel.click()');
3940 event.preventDefault();
3942 dblclick: function(event, dygraph, context) {
3943 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3944 state.log('interactionModel.dblclick()');
3945 NETDATA.resetAllCharts(state);
3947 mousewheel: function(event, dygraph, context) {
3948 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3949 state.log('interactionModel.mousewheel()');
3951 // Take the offset of a mouse event on the dygraph canvas and
3952 // convert it to a pair of percentages from the bottom left.
3953 // (Not top left, bottom is where the lower value is.)
3954 function offsetToPercentage(g, offsetX, offsetY) {
3955 // This is calculating the pixel offset of the leftmost date.
3956 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
3957 var yar0 = g.yAxisRange(0);
3959 // This is calculating the pixel of the higest value. (Top pixel)
3960 var yOffset = g.toDomCoords(null, yar0[1])[1];
3962 // x y w and h are relative to the corner of the drawing area,
3963 // so that the upper corner of the drawing area is (0, 0).
3964 var x = offsetX - xOffset;
3965 var y = offsetY - yOffset;
3967 // This is computing the rightmost pixel, effectively defining the
3969 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
3971 // This is computing the lowest pixel, effectively defining the height.
3972 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
3974 // Percentage from the left.
3975 var xPct = w === 0 ? 0 : (x / w);
3976 // Percentage from the top.
3977 var yPct = h === 0 ? 0 : (y / h);
3979 // The (1-) part below changes it from "% distance down from the top"
3980 // to "% distance up from the bottom".
3981 return [xPct, (1-yPct)];
3984 // Adjusts [x, y] toward each other by zoomInPercentage%
3985 // Split it so the left/bottom axis gets xBias/yBias of that change and
3986 // tight/top gets (1-xBias)/(1-yBias) of that change.
3988 // If a bias is missing it splits it down the middle.
3989 function zoomRange(g, zoomInPercentage, xBias, yBias) {
3990 xBias = xBias || 0.5;
3991 yBias = yBias || 0.5;
3993 function adjustAxis(axis, zoomInPercentage, bias) {
3994 var delta = axis[1] - axis[0];
3995 var increment = delta * zoomInPercentage;
3996 var foo = [increment * bias, increment * (1-bias)];
3998 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4001 var yAxes = g.yAxisRanges();
4003 for (var i = 0; i < yAxes.length; i++) {
4004 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4007 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4010 if(event.altKey || event.shiftKey) {
4011 state.dygraph_user_action = true;
4013 state.globalSelectionSyncStop();
4014 state.globalSelectionSyncDelay();
4016 // http://dygraphs.com/gallery/interaction-api.js
4017 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4018 var percentage = normal / 50;
4020 if (!(event.offsetX && event.offsetY)){
4021 event.offsetX = event.layerX - event.target.offsetLeft;
4022 event.offsetY = event.layerY - event.target.offsetTop;
4025 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4026 var xPct = percentages[0];
4027 var yPct = percentages[1];
4029 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4031 var after = new_x_range[0];
4032 var before = new_x_range[1];
4034 var first = state.netdata_first + state.data_update_every;
4035 var last = state.netdata_last + state.data_update_every;
4038 after -= (before - last);
4045 state.setMode('zoom');
4046 if(state.updateChartPanOrZoom(after, before) === true)
4047 dygraph.updateOptions({ dateWindow: [ after, before ] });
4049 event.preventDefault();
4052 touchstart: function(event, dygraph, context) {
4053 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4054 state.log('interactionModel.touchstart()');
4056 state.dygraph_user_action = true;
4057 state.setMode('zoom');
4060 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4062 // we overwrite the touch directions at the end, to overwrite
4063 // the internal default of dygraphs
4064 context.touchDirections = { x: true, y: false };
4066 state.dygraph_last_touch_start = new Date().getTime();
4067 state.dygraph_last_touch_move = 0;
4069 if(typeof event.touches[0].pageX === 'number')
4070 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4072 state.dygraph_last_touch_page_x = 0;
4074 touchmove: function(event, dygraph, context) {
4075 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4076 state.log('interactionModel.touchmove()');
4078 state.dygraph_user_action = true;
4079 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4081 state.dygraph_last_touch_move = new Date().getTime();
4083 touchend: function(event, dygraph, context) {
4084 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4085 state.log('interactionModel.touchend()');
4087 state.dygraph_user_action = true;
4088 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4090 // if it didn't move, it is a selection
4091 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4092 // internal api of dygraphs
4093 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4094 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4095 if(NETDATA.dygraphSetSelection(state, t) === true)
4096 state.globalSelectionSync(t);
4099 // if it was double tap within double click time, reset the charts
4100 var now = new Date().getTime();
4101 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4102 if(state.dygraph_last_touch_move === 0) {
4103 var dt = now - state.dygraph_last_touch_end;
4104 if(dt <= NETDATA.options.current.double_click_speed)
4105 NETDATA.resetAllCharts(state);
4109 // remember the timestamp of the last touch end
4110 state.dygraph_last_touch_end = now;
4115 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4116 state.dygraph_options.drawGrid = false;
4117 state.dygraph_options.drawAxis = false;
4118 state.dygraph_options.title = undefined;
4119 state.dygraph_options.units = undefined;
4120 state.dygraph_options.ylabel = undefined;
4121 state.dygraph_options.yLabelWidth = 0;
4122 state.dygraph_options.labelsDivWidth = 120;
4123 state.dygraph_options.labelsDivStyles.width = '120px';
4124 state.dygraph_options.labelsSeparateLines = true;
4125 state.dygraph_options.rightGap = 0;
4128 if(smooth === true) {
4129 state.dygraph_smooth_eligible = true;
4131 if(NETDATA.options.current.smooth_plot === true)
4132 state.dygraph_options.plotter = smoothPlotter;
4134 else state.dygraph_smooth_eligible = false;
4136 state.dygraph_instance = new Dygraph(state.element_chart,
4137 data.result.data, state.dygraph_options);
4139 state.dygraph_force_zoom = false;
4140 state.dygraph_user_action = false;
4141 state.dygraph_last_rendered = new Date().getTime();
4145 // ----------------------------------------------------------------------------------------------------------------
4148 NETDATA.morrisInitialize = function(callback) {
4149 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4151 // morris requires raphael
4152 if(!NETDATA.chartLibraries.raphael.initialized) {
4153 if(NETDATA.chartLibraries.raphael.enabled) {
4154 NETDATA.raphaelInitialize(function() {
4155 NETDATA.morrisInitialize(callback);
4159 NETDATA.chartLibraries.morris.enabled = false;
4160 if(typeof callback === "function")
4165 NETDATA._loadCSS(NETDATA.morris_css);
4168 url: NETDATA.morris_js,
4173 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4176 NETDATA.chartLibraries.morris.enabled = false;
4177 NETDATA.error(100, NETDATA.morris_js);
4179 .always(function() {
4180 if(typeof callback === "function")
4186 NETDATA.chartLibraries.morris.enabled = false;
4187 if(typeof callback === "function")
4192 NETDATA.morrisChartUpdate = function(state, data) {
4193 state.morris_instance.setData(data.result.data);
4197 NETDATA.morrisChartCreate = function(state, data) {
4199 state.morris_options = {
4200 element: state.element_chart.id,
4201 data: data.result.data,
4203 ykeys: data.dimension_names,
4204 labels: data.dimension_names,
4210 continuousLine: false,
4211 behaveLikeLine: false
4214 if(state.chart.chart_type === 'line')
4215 state.morris_instance = new Morris.Line(state.morris_options);
4217 else if(state.chart.chart_type === 'area') {
4218 state.morris_options.behaveLikeLine = true;
4219 state.morris_instance = new Morris.Area(state.morris_options);
4222 state.morris_instance = new Morris.Area(state.morris_options);
4227 // ----------------------------------------------------------------------------------------------------------------
4230 NETDATA.raphaelInitialize = function(callback) {
4231 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4233 url: NETDATA.raphael_js,
4238 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4241 NETDATA.chartLibraries.raphael.enabled = false;
4242 NETDATA.error(100, NETDATA.raphael_js);
4244 .always(function() {
4245 if(typeof callback === "function")
4250 NETDATA.chartLibraries.raphael.enabled = false;
4251 if(typeof callback === "function")
4256 NETDATA.raphaelChartUpdate = function(state, data) {
4257 $(state.element_chart).raphael(data.result, {
4258 width: state.chartWidth(),
4259 height: state.chartHeight()
4265 NETDATA.raphaelChartCreate = function(state, data) {
4266 $(state.element_chart).raphael(data.result, {
4267 width: state.chartWidth(),
4268 height: state.chartHeight()
4274 // ----------------------------------------------------------------------------------------------------------------
4277 NETDATA.c3Initialize = function(callback) {
4278 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4281 if(!NETDATA.chartLibraries.d3.initialized) {
4282 if(NETDATA.chartLibraries.d3.enabled) {
4283 NETDATA.d3Initialize(function() {
4284 NETDATA.c3Initialize(callback);
4288 NETDATA.chartLibraries.c3.enabled = false;
4289 if(typeof callback === "function")
4294 NETDATA._loadCSS(NETDATA.c3_css);
4302 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4305 NETDATA.chartLibraries.c3.enabled = false;
4306 NETDATA.error(100, NETDATA.c3_js);
4308 .always(function() {
4309 if(typeof callback === "function")
4315 NETDATA.chartLibraries.c3.enabled = false;
4316 if(typeof callback === "function")
4321 NETDATA.c3ChartUpdate = function(state, data) {
4322 state.c3_instance.destroy();
4323 return NETDATA.c3ChartCreate(state, data);
4325 //state.c3_instance.load({
4326 // rows: data.result,
4333 NETDATA.c3ChartCreate = function(state, data) {
4335 state.element_chart.id = 'c3-' + state.uuid;
4336 // console.log('id = ' + state.element_chart.id);
4338 state.c3_instance = c3.generate({
4339 bindto: '#' + state.element_chart.id,
4341 width: state.chartWidth(),
4342 height: state.chartHeight()
4345 pattern: state.chartColors()
4350 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4356 format: function(x) {
4357 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4384 // console.log(state.c3_instance);
4389 // ----------------------------------------------------------------------------------------------------------------
4392 NETDATA.d3Initialize = function(callback) {
4393 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4400 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4403 NETDATA.chartLibraries.d3.enabled = false;
4404 NETDATA.error(100, NETDATA.d3_js);
4406 .always(function() {
4407 if(typeof callback === "function")
4412 NETDATA.chartLibraries.d3.enabled = false;
4413 if(typeof callback === "function")
4418 NETDATA.d3ChartUpdate = function(state, data) {
4422 NETDATA.d3ChartCreate = function(state, data) {
4426 // ----------------------------------------------------------------------------------------------------------------
4429 NETDATA.googleInitialize = function(callback) {
4430 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4432 url: NETDATA.google_js,
4437 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4438 google.load('visualization', '1.1', {
4439 'packages': ['corechart', 'controls'],
4440 'callback': callback
4444 NETDATA.chartLibraries.google.enabled = false;
4445 NETDATA.error(100, NETDATA.google_js);
4446 if(typeof callback === "function")
4451 NETDATA.chartLibraries.google.enabled = false;
4452 if(typeof callback === "function")
4457 NETDATA.googleChartUpdate = function(state, data) {
4458 var datatable = new google.visualization.DataTable(data.result);
4459 state.google_instance.draw(datatable, state.google_options);
4463 NETDATA.googleChartCreate = function(state, data) {
4464 var datatable = new google.visualization.DataTable(data.result);
4466 state.google_options = {
4467 colors: state.chartColors(),
4469 // do not set width, height - the chart resizes itself
4470 //width: state.chartWidth(),
4471 //height: state.chartHeight(),
4476 // title: "Time of Day",
4477 // format:'HH:mm:ss',
4478 viewWindowMode: 'maximized',
4490 viewWindowMode: 'pretty',
4505 focusTarget: 'category',
4512 titlePosition: 'out',
4523 curveType: 'function',
4528 switch(state.chart.chart_type) {
4530 state.google_options.vAxis.viewWindowMode = 'maximized';
4531 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4532 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4536 state.google_options.isStacked = true;
4537 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4538 state.google_options.vAxis.viewWindowMode = 'maximized';
4539 state.google_options.vAxis.minValue = null;
4540 state.google_options.vAxis.maxValue = null;
4541 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4546 state.google_options.lineWidth = 2;
4547 state.google_instance = new google.visualization.LineChart(state.element_chart);
4551 state.google_instance.draw(datatable, state.google_options);
4555 // ----------------------------------------------------------------------------------------------------------------
4557 NETDATA.percentFromValueMax = function(value, max) {
4558 if(value === null) value = 0;
4559 if(max < value) max = value;
4563 pcent = Math.round(value * 100 / max);
4564 if(pcent === 0 && value > 0) pcent = 1;
4570 // ----------------------------------------------------------------------------------------------------------------
4573 NETDATA.easypiechartInitialize = function(callback) {
4574 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4576 url: NETDATA.easypiechart_js,
4581 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4584 NETDATA.chartLibraries.easypiechart.enabled = false;
4585 NETDATA.error(100, NETDATA.easypiechart_js);
4587 .always(function() {
4588 if(typeof callback === "function")
4593 NETDATA.chartLibraries.easypiechart.enabled = false;
4594 if(typeof callback === "function")
4599 NETDATA.easypiechartClearSelection = function(state) {
4600 if(typeof state.easyPieChartEvent !== 'undefined') {
4601 if(state.easyPieChartEvent.timer !== null)
4602 clearTimeout(state.easyPieChartEvent.timer);
4604 state.easyPieChartEvent.timer = null;
4607 if(state.isAutoRefreshed() === true && state.data !== null) {
4608 NETDATA.easypiechartChartUpdate(state, state.data);
4611 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4612 state.easyPieChart_instance.update(0);
4614 state.easyPieChart_instance.enableAnimation();
4619 NETDATA.easypiechartSetSelection = function(state, t) {
4620 if(state.timeIsVisible(t) !== true)
4621 return NETDATA.easypiechartClearSelection(state);
4623 var slot = state.calculateRowForTime(t);
4624 if(slot < 0 || slot >= state.data.result.length)
4625 return NETDATA.easypiechartClearSelection(state);
4627 if(typeof state.easyPieChartEvent === 'undefined') {
4628 state.easyPieChartEvent = {
4635 var value = state.data.result[state.data.result.length - 1 - slot];
4636 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4637 var pcent = NETDATA.percentFromValueMax(value, max);
4639 state.easyPieChartEvent.value = value;
4640 state.easyPieChartEvent.pcent = pcent;
4641 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4643 if(state.easyPieChartEvent.timer === null) {
4644 state.easyPieChart_instance.disableAnimation();
4646 state.easyPieChartEvent.timer = setTimeout(function() {
4647 state.easyPieChartEvent.timer = null;
4648 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4649 }, NETDATA.options.current.charts_selection_animation_delay);
4655 NETDATA.easypiechartChartUpdate = function(state, data) {
4656 var value, max, pcent;
4658 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
4664 value = data.result[0];
4665 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4666 pcent = NETDATA.percentFromValueMax(value, max);
4669 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4670 state.easyPieChart_instance.update(pcent);
4674 NETDATA.easypiechartChartCreate = function(state, data) {
4675 var self = $(state.element);
4676 var chart = $(state.element_chart);
4678 var value = data.result[0];
4679 var max = self.data('easypiechart-max-value') || null;
4680 var adjust = self.data('easypiechart-adjust') || null;
4684 state.easyPieChartMax = null;
4687 state.easyPieChartMax = max;
4689 var pcent = NETDATA.percentFromValueMax(value, max);
4691 chart.data('data-percent', pcent);
4695 case 'width': size = state.chartHeight(); break;
4696 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4697 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4699 default: size = state.chartWidth(); break;
4701 state.element.style.width = size + 'px';
4702 state.element.style.height = size + 'px';
4704 var stroke = Math.floor(size / 22);
4705 if(stroke < 3) stroke = 2;
4707 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4708 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4709 state.easyPieChartLabel = document.createElement('span');
4710 state.easyPieChartLabel.className = 'easyPieChartLabel';
4711 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4712 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4713 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4714 state.element_chart.appendChild(state.easyPieChartLabel);
4716 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4717 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4718 state.easyPieChartTitle = document.createElement('span');
4719 state.easyPieChartTitle.className = 'easyPieChartTitle';
4720 state.easyPieChartTitle.innerHTML = state.title;
4721 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4722 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4723 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4724 state.element_chart.appendChild(state.easyPieChartTitle);
4726 var unitfontsize = Math.round(titlefontsize * 0.9);
4727 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4728 state.easyPieChartUnits = document.createElement('span');
4729 state.easyPieChartUnits.className = 'easyPieChartUnits';
4730 state.easyPieChartUnits.innerHTML = state.units;
4731 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4732 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4733 state.element_chart.appendChild(state.easyPieChartUnits);
4735 chart.easyPieChart({
4736 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4737 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4738 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4739 scaleLength: self.data('easypiechart-scalelength') || 5,
4740 lineCap: self.data('easypiechart-linecap') || 'round',
4741 lineWidth: self.data('easypiechart-linewidth') || stroke,
4742 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4743 size: self.data('easypiechart-size') || size,
4744 rotate: self.data('easypiechart-rotate') || 0,
4745 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4746 easing: self.data('easypiechart-easing') || undefined
4749 // when we just re-create the chart
4750 // do not animate the first update
4752 if(typeof state.easyPieChart_instance !== 'undefined')
4755 state.easyPieChart_instance = chart.data('easyPieChart');
4756 if(animate === false) state.easyPieChart_instance.disableAnimation();
4757 state.easyPieChart_instance.update(pcent);
4758 if(animate === false) state.easyPieChart_instance.enableAnimation();
4762 // ----------------------------------------------------------------------------------------------------------------
4765 NETDATA.gaugeInitialize = function(callback) {
4766 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4768 url: NETDATA.gauge_js,
4773 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4776 NETDATA.chartLibraries.gauge.enabled = false;
4777 NETDATA.error(100, NETDATA.gauge_js);
4779 .always(function() {
4780 if(typeof callback === "function")
4785 NETDATA.chartLibraries.gauge.enabled = false;
4786 if(typeof callback === "function")
4791 NETDATA.gaugeAnimation = function(state, status) {
4794 if(typeof status === 'boolean' && status === false)
4796 else if(typeof status === 'number')
4799 state.gauge_instance.animationSpeed = speed;
4800 state.___gaugeOld__.speed = speed;
4803 NETDATA.gaugeSet = function(state, value, min, max) {
4804 if(typeof value !== 'number') value = 0;
4805 if(typeof min !== 'number') min = 0;
4806 if(typeof max !== 'number') max = 0;
4807 if(value > max) max = value;
4808 if(value < min) min = value;
4817 // gauge.js has an issue if the needle
4818 // is smaller than min or larger than max
4819 // when we set the new values
4820 // the needle will go crazy
4822 // to prevent it, we always feed it
4823 // with a percentage, so that the needle
4824 // is always between min and max
4825 var pcent = (value - min) * 100 / (max - min);
4827 // these should never happen
4828 if(pcent < 0) pcent = 0;
4829 if(pcent > 100) pcent = 100;
4831 state.gauge_instance.set(pcent);
4833 state.___gaugeOld__.value = value;
4834 state.___gaugeOld__.min = min;
4835 state.___gaugeOld__.max = max;
4838 NETDATA.gaugeSetLabels = function(state, value, min, max) {
4839 if(state.___gaugeOld__.valueLabel !== value) {
4840 state.___gaugeOld__.valueLabel = value;
4841 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
4843 if(state.___gaugeOld__.minLabel !== min) {
4844 state.___gaugeOld__.minLabel = min;
4845 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
4847 if(state.___gaugeOld__.maxLabel !== max) {
4848 state.___gaugeOld__.maxLabel = max;
4849 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
4853 NETDATA.gaugeClearSelection = function(state) {
4854 if(typeof state.gaugeEvent !== 'undefined') {
4855 if(state.gaugeEvent.timer !== null)
4856 clearTimeout(state.gaugeEvent.timer);
4858 state.gaugeEvent.timer = null;
4861 if(state.isAutoRefreshed() === true && state.data !== null) {
4862 NETDATA.gaugeChartUpdate(state, state.data);
4865 NETDATA.gaugeAnimation(state, false);
4866 NETDATA.gaugeSet(state, null, null, null);
4867 NETDATA.gaugeSetLabels(state, null, null, null);
4870 NETDATA.gaugeAnimation(state, true);
4874 NETDATA.gaugeSetSelection = function(state, t) {
4875 if(state.timeIsVisible(t) !== true)
4876 return NETDATA.gaugeClearSelection(state);
4878 var slot = state.calculateRowForTime(t);
4879 if(slot < 0 || slot >= state.data.result.length)
4880 return NETDATA.gaugeClearSelection(state);
4882 if(typeof state.gaugeEvent === 'undefined') {
4883 state.gaugeEvent = {
4891 var value = state.data.result[state.data.result.length - 1 - slot];
4892 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
4895 state.gaugeEvent.value = value;
4896 state.gaugeEvent.max = max;
4897 state.gaugeEvent.min = min;
4898 NETDATA.gaugeSetLabels(state, value, min, max);
4900 if(state.gaugeEvent.timer === null) {
4901 NETDATA.gaugeAnimation(state, false);
4903 state.gaugeEvent.timer = setTimeout(function() {
4904 state.gaugeEvent.timer = null;
4905 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
4906 }, NETDATA.options.current.charts_selection_animation_delay);
4912 NETDATA.gaugeChartUpdate = function(state, data) {
4913 var value, min, max;
4915 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
4919 NETDATA.gaugeSetLabels(state, null, null, null);
4922 value = data.result[0];
4924 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
4925 if(value > max) max = value;
4926 NETDATA.gaugeSetLabels(state, value, min, max);
4929 NETDATA.gaugeSet(state, value, min, max);
4933 NETDATA.gaugeChartCreate = function(state, data) {
4934 var self = $(state.element);
4935 // var chart = $(state.element_chart);
4937 var value = data.result[0];
4938 var max = self.data('gauge-max-value') || null;
4939 var adjust = self.data('gauge-adjust') || null;
4940 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
4941 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
4942 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
4943 var stopColor = self.data('gauge-stop-color') || void 0;
4944 var generateGradient = self.data('gauge-generate-gradient') || false;
4948 state.gaugeMax = null;
4951 state.gaugeMax = max;
4953 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
4955 // case 'width': width = height * ratio; break;
4957 // default: height = width / ratio; break;
4959 //state.element.style.width = width.toString() + 'px';
4960 //state.element.style.height = height.toString() + 'px';
4965 lines: 12, // The number of lines to draw
4966 angle: 0.15, // The length of each line
4967 lineWidth: 0.44, // 0.44 The line thickness
4969 length: 0.8, // 0.9 The radius of the inner circle
4970 strokeWidth: 0.035, // The rotation offset
4971 color: pointerColor // Fill color
4973 colorStart: startColor, // Colors
4974 colorStop: stopColor, // just experiment with them
4975 strokeColor: strokeColor, // to see which ones work best for you
4977 generateGradient: generateGradient,
4981 if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
4982 options.percentColors = [
4983 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
4984 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
4985 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
4986 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
4987 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
4988 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
4989 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
4990 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
4991 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
4992 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
4993 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
4996 state.gauge_canvas = document.createElement('canvas');
4997 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
4998 state.gauge_canvas.className = 'gaugeChart';
4999 state.gauge_canvas.width = width;
5000 state.gauge_canvas.height = height;
5001 state.element_chart.appendChild(state.gauge_canvas);
5003 var valuefontsize = Math.floor(height / 6);
5004 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5005 state.gaugeChartLabel = document.createElement('span');
5006 state.gaugeChartLabel.className = 'gaugeChartLabel';
5007 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5008 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5009 state.element_chart.appendChild(state.gaugeChartLabel);
5011 var titlefontsize = Math.round(valuefontsize / 2);
5013 state.gaugeChartTitle = document.createElement('span');
5014 state.gaugeChartTitle.className = 'gaugeChartTitle';
5015 state.gaugeChartTitle.innerHTML = state.title;
5016 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5017 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5018 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5019 state.element_chart.appendChild(state.gaugeChartTitle);
5021 var unitfontsize = Math.round(titlefontsize * 0.9);
5022 state.gaugeChartUnits = document.createElement('span');
5023 state.gaugeChartUnits.className = 'gaugeChartUnits';
5024 state.gaugeChartUnits.innerHTML = state.units;
5025 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5026 state.element_chart.appendChild(state.gaugeChartUnits);
5028 state.gaugeChartMin = document.createElement('span');
5029 state.gaugeChartMin.className = 'gaugeChartMin';
5030 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5031 state.element_chart.appendChild(state.gaugeChartMin);
5033 state.gaugeChartMax = document.createElement('span');
5034 state.gaugeChartMax.className = 'gaugeChartMax';
5035 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5036 state.element_chart.appendChild(state.gaugeChartMax);
5038 // when we just re-create the chart
5039 // do not animate the first update
5041 if(typeof state.gauge_instance !== 'undefined')
5044 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5046 state.___gaugeOld__ = {
5055 // we will always feed a percentage
5056 state.gauge_instance.minValue = 0;
5057 state.gauge_instance.maxValue = 100;
5059 NETDATA.gaugeAnimation(state, animate);
5060 NETDATA.gaugeSet(state, value, 0, max);
5061 NETDATA.gaugeSetLabels(state, value, 0, max);
5062 NETDATA.gaugeAnimation(state, true);
5066 // ----------------------------------------------------------------------------------------------------------------
5067 // Charts Libraries Registration
5069 NETDATA.chartLibraries = {
5071 initialize: NETDATA.dygraphInitialize,
5072 create: NETDATA.dygraphChartCreate,
5073 update: NETDATA.dygraphChartUpdate,
5074 resize: function(state) {
5075 if(typeof state.dygraph_instance.resize === 'function')
5076 state.dygraph_instance.resize();
5078 setSelection: NETDATA.dygraphSetSelection,
5079 clearSelection: NETDATA.dygraphClearSelection,
5080 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5083 format: function(state) { return 'json'; },
5084 options: function(state) { return 'ms|flip'; },
5085 legend: function(state) {
5086 if(this.isSparkline(state) === false)
5087 return 'right-side';
5091 autoresize: function(state) { return true; },
5092 max_updates_to_recreate: function(state) { return 5000; },
5093 track_colors: function(state) { return true; },
5094 pixels_per_point: function(state) {
5095 if(this.isSparkline(state) === false)
5101 isSparkline: function(state) {
5102 if(typeof state.dygraph_sparkline === 'undefined') {
5103 var t = $(state.element).data('dygraph-theme');
5104 if(t === 'sparkline')
5105 state.dygraph_sparkline = true;
5107 state.dygraph_sparkline = false;
5109 return state.dygraph_sparkline;
5113 initialize: NETDATA.sparklineInitialize,
5114 create: NETDATA.sparklineChartCreate,
5115 update: NETDATA.sparklineChartUpdate,
5117 setSelection: undefined, // function(state, t) { return true; },
5118 clearSelection: undefined, // function(state) { return true; },
5119 toolboxPanAndZoom: null,
5122 format: function(state) { return 'array'; },
5123 options: function(state) { return 'flip|abs'; },
5124 legend: function(state) { return null; },
5125 autoresize: function(state) { return false; },
5126 max_updates_to_recreate: function(state) { return 5000; },
5127 track_colors: function(state) { return false; },
5128 pixels_per_point: function(state) { return 3; }
5131 initialize: NETDATA.peityInitialize,
5132 create: NETDATA.peityChartCreate,
5133 update: NETDATA.peityChartUpdate,
5135 setSelection: undefined, // function(state, t) { return true; },
5136 clearSelection: undefined, // function(state) { return true; },
5137 toolboxPanAndZoom: null,
5140 format: function(state) { return 'ssvcomma'; },
5141 options: function(state) { return 'null2zero|flip|abs'; },
5142 legend: function(state) { return null; },
5143 autoresize: function(state) { return false; },
5144 max_updates_to_recreate: function(state) { return 5000; },
5145 track_colors: function(state) { return false; },
5146 pixels_per_point: function(state) { return 3; }
5149 initialize: NETDATA.morrisInitialize,
5150 create: NETDATA.morrisChartCreate,
5151 update: NETDATA.morrisChartUpdate,
5153 setSelection: undefined, // function(state, t) { return true; },
5154 clearSelection: undefined, // function(state) { return true; },
5155 toolboxPanAndZoom: null,
5158 format: function(state) { return 'json'; },
5159 options: function(state) { return 'objectrows|ms'; },
5160 legend: function(state) { return null; },
5161 autoresize: function(state) { return false; },
5162 max_updates_to_recreate: function(state) { return 50; },
5163 track_colors: function(state) { return false; },
5164 pixels_per_point: function(state) { return 15; }
5167 initialize: NETDATA.googleInitialize,
5168 create: NETDATA.googleChartCreate,
5169 update: NETDATA.googleChartUpdate,
5171 setSelection: undefined, //function(state, t) { return true; },
5172 clearSelection: undefined, //function(state) { return true; },
5173 toolboxPanAndZoom: null,
5176 format: function(state) { return 'datatable'; },
5177 options: function(state) { return ''; },
5178 legend: function(state) { return null; },
5179 autoresize: function(state) { return false; },
5180 max_updates_to_recreate: function(state) { return 300; },
5181 track_colors: function(state) { return false; },
5182 pixels_per_point: function(state) { return 4; }
5185 initialize: NETDATA.raphaelInitialize,
5186 create: NETDATA.raphaelChartCreate,
5187 update: NETDATA.raphaelChartUpdate,
5189 setSelection: undefined, // function(state, t) { return true; },
5190 clearSelection: undefined, // function(state) { return true; },
5191 toolboxPanAndZoom: null,
5194 format: function(state) { return 'json'; },
5195 options: function(state) { return ''; },
5196 legend: function(state) { return null; },
5197 autoresize: function(state) { return false; },
5198 max_updates_to_recreate: function(state) { return 5000; },
5199 track_colors: function(state) { return false; },
5200 pixels_per_point: function(state) { return 3; }
5203 initialize: NETDATA.c3Initialize,
5204 create: NETDATA.c3ChartCreate,
5205 update: NETDATA.c3ChartUpdate,
5207 setSelection: undefined, // function(state, t) { return true; },
5208 clearSelection: undefined, // function(state) { return true; },
5209 toolboxPanAndZoom: null,
5212 format: function(state) { return 'csvjsonarray'; },
5213 options: function(state) { return 'milliseconds'; },
5214 legend: function(state) { return null; },
5215 autoresize: function(state) { return false; },
5216 max_updates_to_recreate: function(state) { return 5000; },
5217 track_colors: function(state) { return false; },
5218 pixels_per_point: function(state) { return 15; }
5221 initialize: NETDATA.d3Initialize,
5222 create: NETDATA.d3ChartCreate,
5223 update: NETDATA.d3ChartUpdate,
5225 setSelection: undefined, // function(state, t) { return true; },
5226 clearSelection: undefined, // function(state) { return true; },
5227 toolboxPanAndZoom: null,
5230 format: function(state) { return 'json'; },
5231 options: function(state) { return ''; },
5232 legend: function(state) { return null; },
5233 autoresize: function(state) { return false; },
5234 max_updates_to_recreate: function(state) { return 5000; },
5235 track_colors: function(state) { return false; },
5236 pixels_per_point: function(state) { return 3; }
5239 initialize: NETDATA.easypiechartInitialize,
5240 create: NETDATA.easypiechartChartCreate,
5241 update: NETDATA.easypiechartChartUpdate,
5243 setSelection: NETDATA.easypiechartSetSelection,
5244 clearSelection: NETDATA.easypiechartClearSelection,
5245 toolboxPanAndZoom: null,
5248 format: function(state) { return 'array'; },
5249 options: function(state) { return 'absolute'; },
5250 legend: function(state) { return null; },
5251 autoresize: function(state) { return false; },
5252 max_updates_to_recreate: function(state) { return 5000; },
5253 track_colors: function(state) { return true; },
5254 pixels_per_point: function(state) { return 3; },
5258 initialize: NETDATA.gaugeInitialize,
5259 create: NETDATA.gaugeChartCreate,
5260 update: NETDATA.gaugeChartUpdate,
5262 setSelection: NETDATA.gaugeSetSelection,
5263 clearSelection: NETDATA.gaugeClearSelection,
5264 toolboxPanAndZoom: null,
5267 format: function(state) { return 'array'; },
5268 options: function(state) { return 'absolute'; },
5269 legend: function(state) { return null; },
5270 autoresize: function(state) { return false; },
5271 max_updates_to_recreate: function(state) { return 5000; },
5272 track_colors: function(state) { return true; },
5273 pixels_per_point: function(state) { return 3; },
5278 NETDATA.registerChartLibrary = function(library, url) {
5279 if(NETDATA.options.debug.libraries === true)
5280 console.log("registering chart library: " + library);
5282 NETDATA.chartLibraries[library].url = url;
5283 NETDATA.chartLibraries[library].initialized = true;
5284 NETDATA.chartLibraries[library].enabled = true;
5287 // ----------------------------------------------------------------------------------------------------------------
5290 NETDATA.requiredJs = [
5292 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5293 isAlreadyLoaded: function() {
5294 if(typeof $().emulateTransitionEnd == 'function')
5297 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5305 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5306 isAlreadyLoaded: function() { return false; }
5309 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5310 isAlreadyLoaded: function() { return false; }
5314 NETDATA.requiredCSS = [
5316 url: NETDATA.themes.current.bootstrap_css,
5317 isAlreadyLoaded: function() {
5318 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5325 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5326 isAlreadyLoaded: function() { return false; }
5329 url: NETDATA.themes.current.dashboard_css,
5330 isAlreadyLoaded: function() { return false; }
5333 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5334 isAlreadyLoaded: function() { return false; }
5338 NETDATA.loadRequiredJs = function(index, callback) {
5339 if(index >= NETDATA.requiredJs.length) {
5340 if(typeof callback === 'function')
5345 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5346 NETDATA.loadRequiredJs(++index, callback);
5350 if(NETDATA.options.debug.main_loop === true)
5351 console.log('loading ' + NETDATA.requiredJs[index].url);
5354 url: NETDATA.requiredJs[index].url,
5358 .success(function() {
5359 if(NETDATA.options.debug.main_loop === true)
5360 console.log('loaded ' + NETDATA.requiredJs[index].url);
5362 NETDATA.loadRequiredJs(++index, callback);
5365 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5369 NETDATA.loadRequiredCSS = function(index) {
5370 if(index >= NETDATA.requiredCSS.length)
5373 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5374 NETDATA.loadRequiredCSS(++index);
5378 if(NETDATA.options.debug.main_loop === true)
5379 console.log('loading ' + NETDATA.requiredCSS[index].url);
5381 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5382 NETDATA.loadRequiredCSS(++index);
5385 NETDATA.errorReset();
5386 NETDATA.loadRequiredCSS(0);
5388 NETDATA._loadjQuery(function() {
5389 NETDATA.loadRequiredJs(0, function() {
5390 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
5391 if(NETDATA.options.debug.main_loop === true)
5392 console.log('starting chart refresh thread');
5399 // window.NETDATA = NETDATA;
5400 // })(window, document);