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
247 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
249 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
250 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
252 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
254 smooth_plot: true, // enable smooth plot, where possible
256 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
258 color_fill_opacity_line: 1.0,
259 color_fill_opacity_area: 0.2,
260 color_fill_opacity_stacked: 0.8,
262 pan_and_zoom_step: 0.1, // the increment when panning and zooming with the toolbox
264 setOptionCallback: function() { ; }
272 chart_data_url: false,
273 chart_errors: false, // FIXME
282 // ----------------------------------------------------------------------------------------------------------------
283 // local storage options
285 NETDATA.localStorage = {
288 callback: {} // only used for resetting back to defaults
291 NETDATA.localStorageGet = function(key, def, callback) {
294 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
295 NETDATA.localStorage.default[key.toString()] = def;
296 NETDATA.localStorage.callback[key.toString()] = callback;
299 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
301 // console.log('localStorage: loading "' + key.toString() + '"');
302 ret = localStorage.getItem(key.toString());
303 if(ret === null || ret === 'undefined') {
304 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
305 localStorage.setItem(key.toString(), JSON.stringify(def));
309 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
310 ret = JSON.parse(ret);
311 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
315 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
320 if(typeof ret === 'undefined' || ret === 'undefined') {
321 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
325 NETDATA.localStorage.current[key.toString()] = ret;
329 NETDATA.localStorageSet = function(key, value, callback) {
330 if(typeof value === 'undefined' || value === 'undefined') {
331 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
334 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
335 NETDATA.localStorage.default[key.toString()] = value;
336 NETDATA.localStorage.current[key.toString()] = value;
337 NETDATA.localStorage.callback[key.toString()] = callback;
340 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
341 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
343 localStorage.setItem(key.toString(), JSON.stringify(value));
346 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
350 NETDATA.localStorage.current[key.toString()] = value;
354 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
356 if(typeof obj[i] === 'object') {
357 //console.log('object ' + prefix + '.' + i.toString());
358 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
362 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
366 NETDATA.setOption = function(key, value) {
367 if(key.toString() === 'setOptionCallback') {
368 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
369 NETDATA.options.current[key.toString()] = value;
370 NETDATA.options.current.setOptionCallback();
373 else if(NETDATA.options.current[key.toString()] !== value) {
374 var name = 'options.' + key.toString();
376 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
377 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
379 //console.log(NETDATA.localStorage);
380 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
381 //console.log(NETDATA.options);
382 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
384 if(typeof NETDATA.options.current.setOptionCallback === 'function')
385 NETDATA.options.current.setOptionCallback();
391 NETDATA.getOption = function(key) {
392 return NETDATA.options.current[key.toString()];
395 // read settings from local storage
396 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
398 // always start with this option enabled.
399 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
401 NETDATA.resetOptions = function() {
402 for(var i in NETDATA.localStorage.default) {
403 var a = i.split('.');
405 if(a[0] === 'options') {
406 if(a[1] === 'setOptionCallback') continue;
407 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
408 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
410 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
412 else if(a[0] === 'chart_heights') {
413 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
414 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
420 // ----------------------------------------------------------------------------------------------------------------
422 if(NETDATA.options.debug.main_loop === true)
423 console.log('welcome to NETDATA');
425 NETDATA.onresize = function() {
426 NETDATA.options.last_resized = new Date().getTime();
430 NETDATA.onscroll = function() {
431 // console.log('onscroll');
433 NETDATA.options.last_page_scroll = new Date().getTime();
434 if(NETDATA.options.targets === null) return;
436 // when the user scrolls he sees that we have
437 // hidden all the not-visible charts
438 // using this little function we try to switch
439 // the charts back to visible quickly
440 var targets = NETDATA.options.targets;
441 var len = targets.length;
442 while(len--) targets[len].isVisible();
445 window.onresize = NETDATA.onresize;
446 window.onscroll = NETDATA.onscroll;
448 // ----------------------------------------------------------------------------------------------------------------
451 NETDATA.errorCodes = {
452 100: { message: "Cannot load chart library", alert: true },
453 101: { message: "Cannot load jQuery", alert: true },
454 402: { message: "Chart library not found", alert: false },
455 403: { message: "Chart library not enabled/is failed", alert: false },
456 404: { message: "Chart not found", alert: false }
458 NETDATA.errorLast = {
464 NETDATA.error = function(code, msg) {
465 NETDATA.errorLast.code = code;
466 NETDATA.errorLast.message = msg;
467 NETDATA.errorLast.datetime = new Date().getTime();
469 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
471 if(NETDATA.errorCodes[code].alert)
472 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
475 NETDATA.errorReset = function() {
476 NETDATA.errorLast.code = 0;
477 NETDATA.errorLast.message = "You are doing fine!";
478 NETDATA.errorLast.datetime = 0;
481 // ----------------------------------------------------------------------------------------------------------------
484 // When multiple charts need the same chart, we avoid downloading it
485 // multiple times (and having it in browser memory multiple time)
486 // by using this registry.
488 // Every time we download a chart definition, we save it here with .add()
489 // Then we try to get it back with .get(). If that fails, we download it.
491 NETDATA.chartRegistry = {
494 fixid: function(id) {
495 return id.replace(/:/g, "_").replace(/\//g, "_");
498 add: function(host, id, data) {
499 host = this.fixid(host);
502 if(typeof this.charts[host] === 'undefined')
503 this.charts[host] = {};
505 //console.log('added ' + host + '/' + id);
506 this.charts[host][id] = data;
509 get: function(host, id) {
510 host = this.fixid(host);
513 if(typeof this.charts[host] === 'undefined')
516 if(typeof this.charts[host][id] === 'undefined')
519 //console.log('cached ' + host + '/' + id);
520 return this.charts[host][id];
523 downloadAll: function(host, callback) {
524 while(host.slice(-1) === '/')
525 host = host.substring(0, host.length - 1);
530 url: host + '/api/v1/charts',
531 crossDomain: NETDATA.options.crossDomainAjax,
535 .done(function(data) {
536 var h = NETDATA.chartRegistry.fixid(host);
537 //console.log('downloaded all charts from ' + host + ' (' + h + ')');
538 self.charts[h] = data.charts;
539 if(typeof callback === 'function')
543 if(typeof callback === 'function')
549 // ----------------------------------------------------------------------------------------------------------------
550 // Global Pan and Zoom on charts
552 // Using this structure are synchronize all the charts, so that
553 // when you pan or zoom one, all others are automatically refreshed
554 // to the same timespan.
556 NETDATA.globalPanAndZoom = {
557 seq: 0, // timestamp ms
558 // every time a chart is panned or zoomed
559 // we set the timestamp here
560 // then we use it as a sequence number
561 // to find if other charts are syncronized
564 master: null, // the master chart (state), to which all others
567 force_before_ms: null, // the timespan to sync all other charts
568 force_after_ms: null,
571 setMaster: function(state, after, before) {
572 if(NETDATA.options.current.sync_pan_and_zoom === false)
575 if(this.master !== null && this.master !== state)
576 this.master.resetChart(true, true);
578 var now = new Date().getTime();
581 this.force_after_ms = after;
582 this.force_before_ms = before;
583 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
587 clearMaster: function() {
588 if(this.master !== null) {
589 var st = this.master;
596 this.force_after_ms = null;
597 this.force_before_ms = null;
598 NETDATA.options.auto_refresher_stop_until = 0;
601 // is the given state the master of the global
602 // pan and zoom sync?
603 isMaster: function(state) {
604 if(this.master === state) return true;
608 // are we currently have a global pan and zoom sync?
609 isActive: function() {
610 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
614 // check if a chart, other than the master
615 // needs to be refreshed, due to the global pan and zoom
616 shouldBeAutoRefreshed: function(state) {
617 if(this.master === null || this.seq === 0)
620 //if(state.needsRecreation())
623 if(state.tm.pan_and_zoom_seq === this.seq)
630 // ----------------------------------------------------------------------------------------------------------------
631 // dimensions selection
634 // move color assignment to dimensions, here
636 dimensionStatus = function(parent, label, name_div, value_div, color) {
637 this.enabled = false;
638 this.parent = parent;
640 this.name_div = null;
641 this.value_div = null;
642 this.color = NETDATA.themes.current.foreground;
644 if(parent.selected === parent.unselected)
645 this.selected = true;
647 this.selected = false;
649 this.setOptions(name_div, value_div, color);
652 dimensionStatus.prototype.invalidate = function() {
653 this.name_div = null;
654 this.value_div = null;
655 this.enabled = false;
658 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
661 if(this.name_div != name_div) {
662 this.name_div = name_div;
663 this.name_div.title = this.label;
664 this.name_div.style.color = this.color;
665 if(this.selected === false)
666 this.name_div.className = 'netdata-legend-name not-selected';
668 this.name_div.className = 'netdata-legend-name selected';
671 if(this.value_div != value_div) {
672 this.value_div = value_div;
673 this.value_div.title = this.label;
674 this.value_div.style.color = this.color;
675 if(this.selected === false)
676 this.value_div.className = 'netdata-legend-value not-selected';
678 this.value_div.className = 'netdata-legend-value selected';
685 dimensionStatus.prototype.setHandler = function() {
686 if(this.enabled === false) return;
690 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
691 this.name_div.onclick = this.value_div.onclick = function(e) {
693 if(ds.isSelected()) {
695 if(e.shiftKey === true || e.ctrlKey === true) {
696 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
699 if(ds.parent.countSelected() === 0)
700 ds.parent.selectAll();
703 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
704 if(ds.parent.countSelected() === 1) {
705 ds.parent.selectAll();
708 ds.parent.selectNone();
714 // this is not selected
715 if(e.shiftKey === true || e.ctrlKey === true) {
716 // control or shift key is pressed -> select this too
720 // no key is pressed -> select only this
721 ds.parent.selectNone();
726 ds.parent.state.redrawChart();
730 dimensionStatus.prototype.select = function() {
731 if(this.enabled === false) return;
733 this.name_div.className = 'netdata-legend-name selected';
734 this.value_div.className = 'netdata-legend-value selected';
735 this.selected = true;
738 dimensionStatus.prototype.unselect = function() {
739 if(this.enabled === false) return;
741 this.name_div.className = 'netdata-legend-name not-selected';
742 this.value_div.className = 'netdata-legend-value hidden';
743 this.selected = false;
746 dimensionStatus.prototype.isSelected = function() {
747 return(this.enabled === true && this.selected === true);
750 // ----------------------------------------------------------------------------------------------------------------
752 dimensionsVisibility = function(state) {
755 this.dimensions = {};
756 this.selected_count = 0;
757 this.unselected_count = 0;
760 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
761 if(typeof this.dimensions[label] === 'undefined') {
763 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
766 this.dimensions[label].setOptions(name_div, value_div, color);
768 return this.dimensions[label];
771 dimensionsVisibility.prototype.dimensionGet = function(label) {
772 return this.dimensions[label];
775 dimensionsVisibility.prototype.invalidateAll = function() {
776 for(var d in this.dimensions)
777 this.dimensions[d].invalidate();
780 dimensionsVisibility.prototype.selectAll = function() {
781 for(var d in this.dimensions)
782 this.dimensions[d].select();
785 dimensionsVisibility.prototype.countSelected = function() {
787 for(var d in this.dimensions)
788 if(this.dimensions[d].isSelected()) i++;
793 dimensionsVisibility.prototype.selectNone = function() {
794 for(var d in this.dimensions)
795 this.dimensions[d].unselect();
798 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
799 var ret = new Array();
800 this.selected_count = 0;
801 this.unselected_count = 0;
803 for(var i = 0, len = array.length; i < len ; i++) {
804 var ds = this.dimensions[array[i]];
805 if(typeof ds === 'undefined') {
806 // console.log(array[i] + ' is not found');
811 if(ds.isSelected()) {
813 this.selected_count++;
817 this.unselected_count++;
821 if(this.selected_count === 0 && this.unselected_count !== 0) {
823 return this.selected2BooleanArray(array);
830 // ----------------------------------------------------------------------------------------------------------------
831 // global selection sync
833 NETDATA.globalSelectionSync = {
840 if(this.state !== null)
841 this.state.globalSelectionSyncStop();
845 if(this.state !== null) {
846 this.state.globalSelectionSyncDelay();
851 // ----------------------------------------------------------------------------------------------------------------
852 // Our state object, where all per-chart values are stored
854 chartState = function(element) {
855 var self = $(element);
856 this.element = element;
859 // all private functions should use 'that', instead of 'this'
863 * show an error instead of the chart
865 var error = function(msg) {
866 that.element.innerHTML = that.id + ': ' + msg;
867 that.enabled = false;
868 that.current = that.pan;
871 // GUID - a unique identifier for the chart
872 this.uuid = NETDATA.guid();
874 // string - the name of chart
875 this.id = self.data('netdata');
877 // string - the key for localStorage settings
878 this.settings_id = self.data('id') || null;
880 // the user given dimensions of the element
881 this.width = self.data('width') || NETDATA.chartDefaults.width;
882 this.height = self.data('height') || NETDATA.chartDefaults.height;
884 if(this.settings_id !== null) {
885 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
886 // this is the callback that will be called
887 // if and when the user resets all localStorage variables
890 resizeChartToHeight(height);
894 // string - the netdata server URL, without any path
895 this.host = self.data('host') || NETDATA.chartDefaults.host;
897 // make sure the host does not end with /
898 // all netdata API requests use absolute paths
899 while(this.host.slice(-1) === '/')
900 this.host = this.host.substring(0, this.host.length - 1);
902 // string - the grouping method requested by the user
903 this.method = self.data('method') || NETDATA.chartDefaults.method;
905 // the time-range requested by the user
906 this.after = self.data('after') || NETDATA.chartDefaults.after;
907 this.before = self.data('before') || NETDATA.chartDefaults.before;
909 // the pixels per point requested by the user
910 this.pixels_per_point = self.data('pixels-per-point') || 1;
911 this.points = self.data('points') || null;
913 // the dimensions requested by the user
914 this.dimensions = self.data('dimensions') || null;
916 // the chart library requested by the user
917 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
919 // object - the chart library used
924 this.colors_assigned = {};
925 this.colors_available = null;
927 // the element already created by the user
928 this.element_message = null;
930 // the element with the chart
931 this.element_chart = null;
933 // the element with the legend of the chart (if created by us)
934 this.element_legend = null;
935 this.element_legend_childs = {
945 this.chart_url = null; // string - the url to download chart info
946 this.chart = null; // object - the chart as downloaded from the server
948 this.title = self.data('title') || null; // the title of the chart
949 this.units = self.data('units') || null; // the units of the chart dimensions
950 this.append_options = self.data('append-options') || null; // the units of the chart dimensions
952 this.validated = false; // boolean - has the chart been validated?
953 this.enabled = true; // boolean - is the chart enabled for refresh?
954 this.paused = false; // boolean - is the chart paused for any reason?
955 this.selected = false; // boolean - is the chart shown a selection?
956 this.debug = false; // boolean - console.log() debug info about this chart
958 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
959 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
960 this.requested_after = null; // milliseconds - the timestamp of the request after param
961 this.requested_before = null; // milliseconds - the timestamp of the request before param
962 this.requested_padding = null;
964 this.view_before = 0;
969 force_update_at: 0, // the timestamp to force the update at
970 force_before_ms: null,
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,
988 // this is a pointer to one of the sub-classes below
990 this.current = this.auto;
992 // check the requested library is available
993 // we don't initialize it here - it will be initialized when
994 // this chart will be first used
995 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
996 NETDATA.error(402, that.library_name);
997 error('chart library "' + that.library_name + '" is not found');
1000 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1001 NETDATA.error(403, that.library_name);
1002 error('chart library "' + that.library_name + '" is not enabled');
1006 that.library = NETDATA.chartLibraries[that.library_name];
1008 // milliseconds - the time the last refresh took
1009 this.refresh_dt_ms = 0;
1011 // if we need to report the rendering speed
1012 // find the element that needs to be updated
1013 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1015 if(refresh_dt_element_name !== null)
1016 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1018 this.refresh_dt_element = null;
1020 this.dimensions_visibility = new dimensionsVisibility(this);
1022 this._updating = false;
1024 // ============================================================================================================
1025 // PRIVATE FUNCTIONS
1027 var createDOM = function() {
1028 if(that.enabled === false) return;
1030 if(that.element_message !== null) that.element_message.innerHTML = '';
1031 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1032 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1034 that.element.innerHTML = '';
1036 that.element_message = document.createElement('div');
1037 that.element_message.className = ' netdata-message hidden';
1038 that.element.appendChild(that.element_message);
1040 that.element_chart = document.createElement('div');
1041 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1042 that.element.appendChild(that.element_chart);
1044 if(that.hasLegend() === true) {
1045 that.element.className = "netdata-container-with-legend";
1046 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1048 that.element_legend = document.createElement('div');
1049 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1050 that.element.appendChild(that.element_legend);
1053 that.element.className = "netdata-container";
1054 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1056 that.element_legend = null;
1058 that.element_legend_childs.series = null;
1060 if(typeof(that.width) === 'string')
1061 $(that.element).css('width', that.width);
1062 else if(typeof(that.width) === 'number')
1063 $(that.element).css('width', that.width + 'px');
1065 if(typeof(that.library.aspect_ratio) === 'undefined') {
1066 if(typeof(that.height) === 'string')
1067 $(that.element).css('height', that.height);
1068 else if(typeof(that.height) === 'number')
1069 $(that.element).css('height', that.height + 'px');
1072 var w = that.element.offsetWidth;
1073 if(w === null || w === 0) {
1074 // the div is hidden
1075 // this is resize the chart when next viewed
1076 that.tm.last_resized = 0;
1079 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1082 if(NETDATA.chartDefaults.min_width !== null)
1083 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1085 that.tm.last_dom_created = new Date().getTime();
1091 * initialize state viariables
1092 * destroy all (possibly) created state elements
1093 * create the basic DOM for a chart
1095 var init = function() {
1096 if(that.enabled === false) return;
1098 that.paused = false;
1099 that.selected = false;
1101 that.chart_created = false; // boolean - is the library.create() been called?
1102 that.updates_counter = 0; // numeric - the number of refreshes made so far
1103 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1104 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1107 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1108 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1109 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1111 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1112 last_updated: 0, // the timestamp the chart last updated with data
1113 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1115 // Used with NETDATA.globalPanAndZoom.seq
1116 last_visible_check: 0, // the time we last checked if it is visible
1117 last_resized: 0, // the time the chart was resized
1118 last_hidden: 0, // the time the chart was hidden
1119 last_unhidden: 0, // the time the chart was unhidden
1120 last_autorefreshed: 0 // the time the chart was last refreshed
1123 that.data = null; // the last data as downloaded from the netdata server
1124 that.data_url = 'invalid://'; // string - the last url used to update the chart
1125 that.data_points = 0; // number - the number of points returned from netdata
1126 that.data_after = 0; // milliseconds - the first timestamp of the data
1127 that.data_before = 0; // milliseconds - the last timestamp of the data
1128 that.data_update_every = 0; // milliseconds - the frequency to update the data
1130 that.tm.last_initialized = new Date().getTime();
1133 that.setMode('auto');
1136 var maxMessageFontSize = function() {
1137 // normally we want a font size, as tall as the element
1138 var h = that.element_message.clientHeight;
1140 // but give it some air, 20% let's say, or 5 pixels min
1141 var lost = Math.max(h * 0.2, 5);
1144 // center the text, verically
1145 var paddingTop = (lost - 5) / 2;
1147 // but check the width too
1148 // it should fit 10 characters in it
1149 var w = that.element_message.clientWidth / 10;
1151 paddingTop += (h - w) / 2;
1155 // and don't make it too huge
1156 // 5% of the screen size is good
1157 if(h > screen.height / 20) {
1158 paddingTop += (h - (screen.height / 20)) / 2;
1159 h = screen.height / 20;
1163 that.element_message.style.fontSize = h.toString() + 'px';
1164 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1167 var showMessage = function(msg) {
1168 that.element_message.className = 'netdata-message';
1169 that.element_message.innerHTML = msg;
1170 this.element_message.style.fontSize = 'x-small';
1171 that.element_message.style.paddingTop = '0px';
1172 that.___messageHidden___ = undefined;
1175 var showMessageIcon = function(icon) {
1176 that.element_message.innerHTML = icon;
1177 that.element_message.className = 'netdata-message icon';
1178 maxMessageFontSize();
1179 that.___messageHidden___ = undefined;
1182 var hideMessage = function() {
1183 if(typeof that.___messageHidden___ === 'undefined') {
1184 that.___messageHidden___ = true;
1185 that.element_message.className = 'netdata-message hidden';
1189 var showRendering = function() {
1191 if(that.chart !== null) {
1192 if(that.chart.chart_type === 'line')
1193 icon = '<i class="fa fa-line-chart"></i>';
1195 icon = '<i class="fa fa-area-chart"></i>';
1198 icon = '<i class="fa fa-area-chart"></i>';
1200 showMessageIcon(icon + ' netdata');
1203 var showLoading = function() {
1204 if(that.chart_created === false) {
1205 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1211 var isHidden = function() {
1212 if(typeof that.___chartIsHidden___ !== 'undefined')
1218 // hide the chart, when it is not visible - called from isVisible()
1219 var hideChart = function() {
1220 // hide it, if it is not already hidden
1221 if(isHidden() === true) return;
1223 if(that.chart_created === true) {
1224 // we should destroy it
1225 if(NETDATA.options.current.destroy_on_hide === true) {
1230 that.element_chart.style.display = 'none';
1231 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1232 that.tm.last_hidden = new Date().getTime();
1236 that.___chartIsHidden___ = true;
1239 // unhide the chart, when it is visible - called from isVisible()
1240 var unhideChart = function() {
1241 if(isHidden() === false) return;
1243 that.___chartIsHidden___ = undefined;
1244 that.updates_since_last_unhide = 0;
1246 if(that.chart_created === false) {
1247 // we need to re-initialize it, to show our background
1248 // logo in bootstrap tabs, until the chart loads
1252 that.tm.last_unhidden = new Date().getTime();
1253 that.element_chart.style.display = '';
1254 if(that.element_legend !== null) that.element_legend.style.display = '';
1260 var canBeRendered = function() {
1261 if(isHidden() === true || that.isVisible(true) === false)
1267 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1268 var callChartLibraryUpdateSafely = function(data) {
1271 if(canBeRendered() === false)
1274 if(NETDATA.options.debug.chart_errors === true)
1275 status = that.library.update(that, data);
1278 status = that.library.update(that, data);
1285 if(status === false) {
1286 error('chart failed to be updated as ' + that.library_name);
1293 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1294 var callChartLibraryCreateSafely = function(data) {
1297 if(canBeRendered() === false)
1300 if(NETDATA.options.debug.chart_errors === true)
1301 status = that.library.create(that, data);
1304 status = that.library.create(that, data);
1311 if(status === false) {
1312 error('chart failed to be created as ' + that.library_name);
1316 that.chart_created = true;
1317 that.updates_since_last_creation = 0;
1321 // ----------------------------------------------------------------------------------------------------------------
1324 // resizeChart() - private
1325 // to be called just before the chart library to make sure that
1326 // a properly sized dom is available
1327 var resizeChart = function() {
1328 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1329 if(that.chart_created === false) return;
1331 if(that.needsRecreation()) {
1334 else if(typeof that.library.resize === 'function') {
1335 that.library.resize(that);
1337 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1338 $(that.element_legend_childs.nano).nanoScroller();
1340 maxMessageFontSize();
1343 that.tm.last_resized = new Date().getTime();
1347 // this is the actual chart resize algorithm
1349 // - resize the entire container
1350 // - update the internal states
1351 // - resize the chart as the div changes height
1352 // - update the scrollbar of the legend
1353 var resizeChartToHeight = function(h) {
1355 that.element.style.height = h;
1357 if(that.settings_id !== null)
1358 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1360 var now = new Date().getTime();
1361 NETDATA.options.last_page_scroll = now;
1362 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1365 that.tm.last_resized = 0;
1369 this.resizeHandler = function(e) {
1372 if(typeof this.event_resize === 'undefined'
1373 || this.event_resize.chart_original_w === 'undefined'
1374 || this.event_resize.chart_original_h === 'undefined')
1375 this.event_resize = {
1376 chart_original_w: this.element.clientWidth,
1377 chart_original_h: this.element.clientHeight,
1381 if(e.type === 'touchstart') {
1382 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1383 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1386 this.event_resize.mouse_start_x = e.clientX;
1387 this.event_resize.mouse_start_y = e.clientY;
1390 this.event_resize.chart_start_w = this.element.clientWidth;
1391 this.event_resize.chart_start_h = this.element.clientHeight;
1392 this.event_resize.chart_last_w = this.element.clientWidth;
1393 this.event_resize.chart_last_h = this.element.clientHeight;
1395 var now = new Date().getTime();
1396 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1397 // double click / double tap event
1399 // the optimal height of the chart
1400 // showing the entire legend
1401 var optimal = this.event_resize.chart_last_h
1402 + this.element_legend_childs.content.scrollHeight
1403 - this.element_legend_childs.content.clientHeight;
1405 // if we are not optimal, be optimal
1406 if(this.event_resize.chart_last_h != optimal)
1407 resizeChartToHeight(optimal.toString() + 'px');
1409 // else if we do not have the original height
1410 // reset to the original height
1411 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1412 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1415 this.event_resize.last = now;
1417 // process movement event
1418 document.onmousemove =
1419 document.ontouchmove =
1420 this.element_legend_childs.resize_handler.onmousemove =
1421 this.element_legend_childs.resize_handler.ontouchmove =
1426 case 'mousemove': y = e.clientY; break;
1427 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1431 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1433 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1434 resizeChartToHeight(newH.toString() + 'px');
1435 that.event_resize.chart_last_h = newH;
1440 // process end event
1441 document.onmouseup =
1442 document.ontouchend =
1443 this.element_legend_childs.resize_handler.onmouseup =
1444 this.element_legend_childs.resize_handler.ontouchend =
1446 // remove all the hooks
1447 document.onmouseup =
1448 document.onmousemove =
1449 document.ontouchmove =
1450 document.ontouchend =
1451 that.element_legend_childs.resize_handler.onmousemove =
1452 that.element_legend_childs.resize_handler.ontouchmove =
1453 that.element_legend_childs.resize_handler.onmouseout =
1454 that.element_legend_childs.resize_handler.onmouseup =
1455 that.element_legend_childs.resize_handler.ontouchend =
1458 // allow auto-refreshes
1459 NETDATA.options.auto_refresher_stop_until = 0;
1465 var noDataToShow = function() {
1466 showMessageIcon('<i class="fa fa-warning"></i> empty');
1467 that.legendUpdateDOM();
1468 that.tm.last_autorefreshed = new Date().getTime();
1469 // that.data_update_every = 30 * 1000;
1470 //that.element_chart.style.display = 'none';
1471 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1472 //that.___chartIsHidden___ = true;
1475 // ============================================================================================================
1478 this.error = function(msg) {
1482 this.setMode = function(m) {
1483 if(this.current !== null && this.current.name === m) return;
1486 this.current = this.auto;
1487 else if(m === 'pan')
1488 this.current = this.pan;
1489 else if(m === 'zoom')
1490 this.current = this.zoom;
1492 this.current = this.auto;
1494 this.current.force_update_at = 0;
1495 this.current.force_before_ms = null;
1496 this.current.force_after_ms = null;
1498 this.tm.last_mode_switch = new Date().getTime();
1501 // ----------------------------------------------------------------------------------------------------------------
1502 // global selection sync
1504 // prevent to global selection sync for some time
1505 this.globalSelectionSyncDelay = function(ms) {
1506 if(NETDATA.options.current.sync_selection === false)
1509 if(typeof ms === 'number')
1510 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
1512 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
1515 // can we globally apply selection sync?
1516 this.globalSelectionSyncAbility = function() {
1517 if(NETDATA.options.current.sync_selection === false)
1520 if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
1526 this.globalSelectionSyncIsMaster = function() {
1527 if(NETDATA.globalSelectionSync.state === this)
1533 // this chart is the master of the global selection sync
1534 this.globalSelectionSyncBeMaster = function() {
1536 if(this.globalSelectionSyncIsMaster()) {
1537 if(this.debug === true)
1538 this.log('sync: I am the master already.');
1543 if(NETDATA.globalSelectionSync.state) {
1544 if(this.debug === true)
1545 this.log('sync: I am not the sync master. Resetting global sync.');
1547 this.globalSelectionSyncStop();
1550 // become the master
1551 if(this.debug === true)
1552 this.log('sync: becoming sync master.');
1554 this.selected = true;
1555 NETDATA.globalSelectionSync.state = this;
1557 // find the all slaves
1558 var targets = NETDATA.options.targets;
1559 var len = targets.length;
1564 if(this.debug === true)
1565 st.log('sync: not adding me to sync');
1567 else if(st.globalSelectionSyncIsEligible()) {
1568 if(this.debug === true)
1569 st.log('sync: adding to sync as slave');
1571 st.globalSelectionSyncBeSlave();
1575 // this.globalSelectionSyncDelay(100);
1578 // can the chart participate to the global selection sync as a slave?
1579 this.globalSelectionSyncIsEligible = function() {
1580 if(this.enabled === true
1581 && this.library !== null
1582 && typeof this.library.setSelection === 'function'
1583 && this.isVisible() === true
1584 && this.chart_created === true)
1590 // this chart becomes a slave of the global selection sync
1591 this.globalSelectionSyncBeSlave = function() {
1592 if(NETDATA.globalSelectionSync.state !== this)
1593 NETDATA.globalSelectionSync.slaves.push(this);
1596 // sync all the visible charts to the given time
1597 // this is to be called from the chart libraries
1598 this.globalSelectionSync = function(t) {
1599 if(this.globalSelectionSyncAbility() === false) {
1600 if(this.debug === true)
1601 this.log('sync: cannot sync (yet?).');
1606 if(this.globalSelectionSyncIsMaster() === false) {
1607 if(this.debug === true)
1608 this.log('sync: trying to be sync master.');
1610 this.globalSelectionSyncBeMaster();
1612 if(this.globalSelectionSyncAbility() === false) {
1613 if(this.debug === true)
1614 this.log('sync: cannot sync (yet?).');
1620 NETDATA.globalSelectionSync.last_t = t;
1621 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1626 // stop syncing all charts to the given time
1627 this.globalSelectionSyncStop = function() {
1628 if(NETDATA.globalSelectionSync.slaves.length) {
1629 if(this.debug === true)
1630 this.log('sync: cleaning up...');
1632 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1634 if(that.debug === true)
1635 st.log('sync: not adding me to sync stop');
1638 if(that.debug === true)
1639 st.log('sync: removed slave from sync');
1641 st.clearSelection();
1645 NETDATA.globalSelectionSync.last_t = 0;
1646 NETDATA.globalSelectionSync.slaves = [];
1647 NETDATA.globalSelectionSync.state = null;
1650 this.clearSelection();
1653 this.setSelection = function(t) {
1654 if(typeof this.library.setSelection === 'function') {
1655 if(this.library.setSelection(this, t) === true)
1656 this.selected = true;
1658 this.selected = false;
1660 else this.selected = true;
1662 if(this.selected === true && this.debug === true)
1663 this.log('selection set to ' + t.toString());
1665 return this.selected;
1668 this.clearSelection = function() {
1669 if(this.selected === true) {
1670 if(typeof this.library.clearSelection === 'function') {
1671 if(this.library.clearSelection(this) === true)
1672 this.selected = false;
1674 this.selected = true;
1676 else this.selected = false;
1678 if(this.selected === false && this.debug === true)
1679 this.log('selection cleared');
1684 return this.selected;
1687 // find if a timestamp (ms) is shown in the current chart
1688 this.timeIsVisible = function(t) {
1689 if(t >= this.data_after && t <= this.data_before)
1694 this.calculateRowForTime = function(t) {
1695 if(this.timeIsVisible(t) === false) return -1;
1696 return Math.floor((t - this.data_after) / this.data_update_every);
1699 // ----------------------------------------------------------------------------------------------------------------
1702 this.log = function(msg) {
1703 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
1706 this.pauseChart = function() {
1707 if(this.paused === false) {
1708 if(this.debug === true)
1709 this.log('pauseChart()');
1715 this.unpauseChart = function() {
1716 if(this.paused === true) {
1717 if(this.debug === true)
1718 this.log('unpauseChart()');
1720 this.paused = false;
1724 this.resetChart = function(dont_clear_master, dont_update) {
1725 if(this.debug === true)
1726 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
1728 if(typeof dont_clear_master === 'undefined')
1729 dont_clear_master = false;
1731 if(typeof dont_update === 'undefined')
1732 dont_update = false;
1734 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
1735 if(this.debug === true)
1736 this.log('resetChart() diverting to clearMaster().');
1737 // this will call us back with master === true
1738 NETDATA.globalPanAndZoom.clearMaster();
1742 this.clearSelection();
1744 this.tm.pan_and_zoom_seq = 0;
1746 this.setMode('auto');
1747 this.current.force_update_at = 0;
1748 this.current.force_before_ms = null;
1749 this.current.force_after_ms = null;
1750 this.tm.last_autorefreshed = 0;
1751 this.paused = false;
1752 this.selected = false;
1753 this.enabled = true;
1754 // this.debug = false;
1756 // do not update the chart here
1757 // or the chart will flip-flop when it is the master
1758 // of a selection sync and another chart becomes
1761 if(dont_update !== true && this.isVisible() === true) {
1766 this.updateChartPanOrZoom = function(after, before) {
1767 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
1770 if(this.debug === true)
1773 if(before < after) {
1774 this.log(logme + 'flipped parameters, rejecting it.');
1778 if(typeof this.fixed_min_duration === 'undefined')
1779 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1781 var min_duration = this.fixed_min_duration;
1782 var current_duration = Math.round(this.view_before - this.view_after);
1784 // round the numbers
1785 after = Math.round(after);
1786 before = Math.round(before);
1788 // align them to update_every
1789 // stretching them further away
1790 after -= after % this.data_update_every;
1791 before += this.data_update_every - (before % this.data_update_every);
1793 // the final wanted duration
1794 var wanted_duration = before - after;
1796 // to allow panning, accept just a point below our minimum
1797 if((current_duration - this.data_update_every) < min_duration)
1798 min_duration = current_duration - this.data_update_every;
1800 // we do it, but we adjust to minimum size and return false
1801 // when the wanted size is below the current and the minimum
1803 if(wanted_duration < current_duration && wanted_duration < min_duration) {
1804 if(this.debug === true)
1805 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1807 min_duration = this.fixed_min_duration;
1809 var dt = (min_duration - wanted_duration) / 2;
1812 wanted_duration = before - after;
1816 var tolerance = this.data_update_every * 2;
1817 var movement = Math.abs(before - this.view_before);
1819 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
1820 if(this.debug === true)
1821 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);
1825 if(this.current.name === 'auto') {
1826 this.log(logme + 'caller called me with mode: ' + this.current.name);
1827 this.setMode('pan');
1830 if(this.debug === true)
1831 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);
1833 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1834 this.current.force_after_ms = after;
1835 this.current.force_before_ms = before;
1836 NETDATA.globalPanAndZoom.setMaster(this, after, before);
1840 this.legendFormatValue = function(value) {
1841 if(value === null || value === 'undefined') return '-';
1842 if(typeof value !== 'number') return value;
1844 var abs = Math.abs(value);
1845 if(abs >= 1000) return (Math.round(value)).toLocaleString();
1846 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1847 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
1848 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1849 return (Math.round(value * 10000) / 10000).toLocaleString();
1852 this.legendSetLabelValue = function(label, value) {
1853 var series = this.element_legend_childs.series[label];
1854 if(typeof series === 'undefined') return;
1855 if(series.value === null && series.user === null) return;
1857 // if the value has not changed, skip DOM update
1858 //if(series.last === value) return;
1861 if(typeof value === 'number') {
1862 var v = Math.abs(value);
1863 s = r = this.legendFormatValue(value);
1865 if(typeof series.last === 'number') {
1866 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1867 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1868 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1870 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1875 series.last = value;
1878 if(series.value !== null) series.value.innerHTML = s;
1879 if(series.user !== null) series.user.innerHTML = r;
1882 this.legendSetDate = function(ms) {
1883 if(typeof ms !== 'number') {
1884 this.legendShowUndefined();
1888 var d = new Date(ms);
1890 if(this.element_legend_childs.title_date)
1891 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
1893 if(this.element_legend_childs.title_time)
1894 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
1896 if(this.element_legend_childs.title_units)
1897 this.element_legend_childs.title_units.innerHTML = this.units;
1900 this.legendShowUndefined = function() {
1901 if(this.element_legend_childs.title_date)
1902 this.element_legend_childs.title_date.innerHTML = ' ';
1904 if(this.element_legend_childs.title_time)
1905 this.element_legend_childs.title_time.innerHTML = this.chart.name;
1907 if(this.element_legend_childs.title_units)
1908 this.element_legend_childs.title_units.innerHTML = ' ';
1910 if(this.data && this.element_legend_childs.series !== null) {
1911 var labels = this.data.dimension_names;
1912 var i = labels.length;
1914 var label = labels[i];
1916 if(typeof label === 'undefined') continue;
1917 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1918 this.legendSetLabelValue(label, null);
1923 this.legendShowLatestValues = function() {
1924 if(this.chart === null) return;
1925 if(this.selected) return;
1927 if(this.data === null || this.element_legend_childs.series === null) {
1928 this.legendShowUndefined();
1932 var show_undefined = true;
1933 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
1934 show_undefined = false;
1936 if(show_undefined) {
1937 this.legendShowUndefined();
1941 this.legendSetDate(this.view_before);
1943 var labels = this.data.dimension_names;
1944 var i = labels.length;
1946 var label = labels[i];
1948 if(typeof label === 'undefined') continue;
1949 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1952 this.legendSetLabelValue(label, null);
1954 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
1958 this.legendReset = function() {
1959 this.legendShowLatestValues();
1962 // this should be called just ONCE per dimension per chart
1963 this._chartDimensionColor = function(label) {
1964 if(this.colors === null) this.chartColors();
1966 if(typeof this.colors_assigned[label] === 'undefined') {
1967 if(this.colors_available.length === 0) {
1968 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
1969 this.colors_available.push(NETDATA.themes.current.colors[i]);
1972 this.colors_assigned[label] = this.colors_available.shift();
1974 if(this.debug === true)
1975 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
1978 if(this.debug === true)
1979 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
1982 this.colors.push(this.colors_assigned[label]);
1983 return this.colors_assigned[label];
1986 this.chartColors = function() {
1987 if(this.colors !== null) return this.colors;
1989 this.colors = new Array();
1990 this.colors_available = new Array();
1992 var c = $(this.element).data('colors');
1993 // this.log('read colors: ' + c);
1994 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
1995 if(typeof c !== 'string') {
1996 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2003 for(var i = 0, len = c.length; i < len ; i++) {
2005 this.colors_available.push(c[i]);
2006 // this.log('adding color: ' + c[i]);
2012 // push all the standard colors too
2013 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2014 this.colors_available.push(NETDATA.themes.current.colors[i]);
2019 this.legendUpdateDOM = function() {
2022 // check that the legend DOM is up to date for the downloaded dimensions
2023 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2024 // this.log('the legend does not have any series - requesting legend update');
2027 else if(this.data === null) {
2028 // this.log('the chart does not have any data - requesting legend update');
2031 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2035 var labels = this.data.dimension_names.toString();
2036 if(labels !== this.element_legend_childs.series.labels_key) {
2039 if(this.debug === true)
2040 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2044 if(needed === false) {
2045 // make sure colors available
2048 // do we have to update the current values?
2049 // we do this, only when the visible chart is current
2050 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2051 if(this.debug === true)
2052 this.log('chart is in latest position... updating values on legend...');
2054 //var labels = this.data.dimension_names;
2055 //var i = labels.length;
2057 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2061 if(this.colors === null) {
2062 // this is the first time we update the chart
2063 // let's assign colors to all dimensions
2064 if(this.library.track_colors() === true)
2065 for(var dim in this.chart.dimensions)
2066 this._chartDimensionColor(this.chart.dimensions[dim].name);
2068 // we will re-generate the colors for the chart
2069 // based on the selected dimensions
2072 if(this.debug === true)
2073 this.log('updating Legend DOM');
2075 // mark all dimensions as invalid
2076 this.dimensions_visibility.invalidateAll();
2078 var genLabel = function(state, parent, name, count) {
2079 var color = state._chartDimensionColor(name);
2081 var user_element = null;
2082 var user_id = self.data('show-value-of-' + name + '-at') || null;
2083 if(user_id !== null) {
2084 user_element = document.getElementById(user_id) || null;
2085 if(user_element === null)
2086 me.log('Cannot find element with id: ' + user_id);
2089 state.element_legend_childs.series[name] = {
2090 name: document.createElement('span'),
2091 value: document.createElement('span'),
2096 var label = state.element_legend_childs.series[name];
2098 // create the dimension visibility tracking for this label
2099 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2101 var rgb = NETDATA.colorHex2Rgb(color);
2102 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2103 + state.chart.chart_type
2104 + '" style="background-color: '
2105 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2106 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2108 var text = document.createTextNode(' ' + name);
2109 label.name.appendChild(text);
2112 parent.appendChild(document.createElement('br'));
2114 parent.appendChild(label.name);
2115 parent.appendChild(label.value);
2118 var content = document.createElement('div');
2120 if(this.hasLegend()) {
2121 this.element_legend_childs = {
2123 resize_handler: document.createElement('div'),
2124 toolbox: document.createElement('div'),
2125 toolbox_left: document.createElement('div'),
2126 toolbox_right: document.createElement('div'),
2127 toolbox_reset: document.createElement('div'),
2128 toolbox_zoomin: document.createElement('div'),
2129 toolbox_zoomout: document.createElement('div'),
2130 toolbox_volume: document.createElement('div'),
2131 title_date: document.createElement('span'),
2132 title_time: document.createElement('span'),
2133 title_units: document.createElement('span'),
2134 nano: document.createElement('div'),
2136 paneClass: 'netdata-legend-series-pane',
2137 sliderClass: 'netdata-legend-series-slider',
2138 contentClass: 'netdata-legend-series-content',
2139 enabledClass: '__enabled',
2140 flashedClass: '__flashed',
2141 activeClass: '__active',
2143 alwaysVisible: true,
2149 this.element_legend.innerHTML = '';
2151 if(this.library.toolboxPanAndZoom !== null) {
2152 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2153 this.element.appendChild(this.element_legend_childs.toolbox);
2155 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2156 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2157 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2158 this.element_legend_childs.toolbox_left.onclick = function(e) {
2160 var dt = (that.view_before - that.view_after) * NETDATA.options.current.pan_and_zoom_step;
2161 var before = that.view_before - dt;
2162 var after = that.view_after - dt;
2163 if(after >= that.netdata_first)
2164 that.library.toolboxPanAndZoom(that, after, before);
2166 if(NETDATA.options.current.show_help === true)
2167 $(this.element_legend_childs.toolbox_left).popover({
2172 placement: 'bottom',
2175 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>'
2179 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2180 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2181 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2182 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2184 NETDATA.resetAllCharts(that);
2186 if(NETDATA.options.current.show_help === true)
2187 $(this.element_legend_childs.toolbox_reset).popover({
2192 placement: 'bottom',
2194 title: 'Chart Reset',
2195 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>'
2198 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2199 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2200 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2201 this.element_legend_childs.toolbox_right.onclick = function(e) {
2203 var dt = (that.view_before - that.view_after) * NETDATA.options.current.pan_and_zoom_step;
2204 var before = that.view_before + dt;
2205 var after = that.view_after + dt;
2206 if(before <= that.netdata_last)
2207 that.library.toolboxPanAndZoom(that, after, before);
2209 if(NETDATA.options.current.show_help === true)
2210 $(this.element_legend_childs.toolbox_right).popover({
2215 placement: 'bottom',
2218 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>'
2222 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2223 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2224 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2225 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2227 var dt = (that.view_before - that.view_after) * NETDATA.options.current.pan_and_zoom_step;
2228 var before = that.view_before - dt;
2229 var after = that.view_after + dt;
2230 that.library.toolboxPanAndZoom(that, after, before);
2232 if(NETDATA.options.current.show_help === true)
2233 $(this.element_legend_childs.toolbox_zoomin).popover({
2238 placement: 'bottom',
2240 title: 'Chart Zoom In',
2241 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>'
2244 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2245 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2246 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2247 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2249 var dt = (that.view_before - that.view_after) * NETDATA.options.current.pan_and_zoom_step;
2250 var before = that.view_before + dt;
2251 var after = that.view_after - dt;
2253 that.library.toolboxPanAndZoom(that, after, before);
2255 if(NETDATA.options.current.show_help === true)
2256 $(this.element_legend_childs.toolbox_zoomout).popover({
2261 placement: 'bottom',
2263 title: 'Chart Zoom Out',
2264 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>'
2267 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2268 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2269 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2270 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2271 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2272 //e.preventDefault();
2273 //alert('clicked toolbox_volume on ' + that.id);
2277 this.element_legend_childs.toolbox = null;
2278 this.element_legend_childs.toolbox_left = null;
2279 this.element_legend_childs.toolbox_reset = null;
2280 this.element_legend_childs.toolbox_right = null;
2281 this.element_legend_childs.toolbox_zoomin = null;
2282 this.element_legend_childs.toolbox_zoomout = null;
2283 this.element_legend_childs.toolbox_volume = null;
2286 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2287 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2288 this.element.appendChild(this.element_legend_childs.resize_handler);
2289 if(NETDATA.options.current.show_help === true)
2290 $(this.element_legend_childs.resize_handler).popover({
2295 placement: 'bottom',
2297 title: 'Chart Resize',
2298 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>'
2302 this.element_legend_childs.resize_handler.onmousedown =
2304 that.resizeHandler(e);
2308 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2309 that.resizeHandler(e);
2312 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2313 this.element_legend.appendChild(this.element_legend_childs.title_date);
2315 this.element_legend.appendChild(document.createElement('br'));
2317 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2318 this.element_legend.appendChild(this.element_legend_childs.title_time);
2320 this.element_legend.appendChild(document.createElement('br'));
2322 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2323 this.element_legend.appendChild(this.element_legend_childs.title_units);
2325 this.element_legend.appendChild(document.createElement('br'));
2327 this.element_legend_childs.nano.className = 'netdata-legend-series';
2328 this.element_legend.appendChild(this.element_legend_childs.nano);
2330 content.className = 'netdata-legend-series-content';
2331 this.element_legend_childs.nano.appendChild(content);
2333 if(NETDATA.options.current.show_help === true)
2334 $(content).popover({
2339 placement: 'bottom',
2340 title: 'Chart Legend',
2342 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>'
2346 this.element_legend_childs = {
2348 resize_handler: null,
2351 toolbox_right: null,
2352 toolbox_reset: null,
2353 toolbox_zoomin: null,
2354 toolbox_zoomout: null,
2355 toolbox_volume: null,
2366 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2367 if(this.debug === true)
2368 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2370 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2371 genLabel(this, content, this.data.dimension_names[i], i);
2375 var tmp = new Array();
2376 for(var dim in this.chart.dimensions) {
2377 tmp.push(this.chart.dimensions[dim].name);
2378 genLabel(this, content, this.chart.dimensions[dim].name, i);
2380 this.element_legend_childs.series.labels_key = tmp.toString();
2381 if(this.debug === true)
2382 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2385 // create a hidden div to be used for hidding
2386 // the original legend of the chart library
2387 var el = document.createElement('div');
2388 if(this.element_legend !== null)
2389 this.element_legend.appendChild(el);
2390 el.style.display = 'none';
2392 this.element_legend_childs.hidden = document.createElement('div');
2393 el.appendChild(this.element_legend_childs.hidden);
2395 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2396 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2398 this.legendShowLatestValues();
2401 this.hasLegend = function() {
2402 if(typeof this.___hasLegendCache___ !== 'undefined')
2403 return this.___hasLegendCache___;
2406 if(this.library && this.library.legend(this) === 'right-side') {
2407 var legend = $(this.element).data('legend') || 'yes';
2408 if(legend === 'yes') leg = true;
2411 this.___hasLegendCache___ = leg;
2415 this.legendWidth = function() {
2416 return (this.hasLegend())?140:0;
2419 this.legendHeight = function() {
2420 return $(this.element).height();
2423 this.chartWidth = function() {
2424 return $(this.element).width() - this.legendWidth();
2427 this.chartHeight = function() {
2428 return $(this.element).height();
2431 this.chartPixelsPerPoint = function() {
2432 // force an options provided detail
2433 var px = this.pixels_per_point;
2435 if(this.library && px < this.library.pixels_per_point(this))
2436 px = this.library.pixels_per_point(this);
2438 if(px < NETDATA.options.current.pixels_per_point)
2439 px = NETDATA.options.current.pixels_per_point;
2444 this.needsRecreation = function() {
2446 this.chart_created === true
2448 && this.library.autoresize() === false
2449 && this.tm.last_resized < NETDATA.options.last_resized
2453 this.chartURL = function() {
2454 var after, before, points_multiplier = 1;
2455 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2456 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2458 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2459 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2460 this.view_after = after * 1000;
2461 this.view_before = before * 1000;
2463 this.requested_padding = null;
2464 points_multiplier = 1;
2466 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2467 this.tm.pan_and_zoom_seq = 0;
2469 before = Math.round(this.current.force_before_ms / 1000);
2470 after = Math.round(this.current.force_after_ms / 1000);
2471 this.view_after = after * 1000;
2472 this.view_before = before * 1000;
2474 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2475 this.requested_padding = Math.round((before - after) / 2);
2476 after -= this.requested_padding;
2477 before += this.requested_padding;
2478 this.requested_padding *= 1000;
2479 points_multiplier = 2;
2482 this.current.force_before_ms = null;
2483 this.current.force_after_ms = null;
2486 this.tm.pan_and_zoom_seq = 0;
2488 before = this.before;
2490 this.view_after = after * 1000;
2491 this.view_before = before * 1000;
2493 this.requested_padding = null;
2494 points_multiplier = 1;
2497 this.requested_after = after * 1000;
2498 this.requested_before = before * 1000;
2500 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2502 // build the data URL
2503 this.data_url = this.host + this.chart.data_url;
2504 this.data_url += "&format=" + this.library.format();
2505 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2506 this.data_url += "&group=" + this.method;
2507 this.data_url += "&options=" + this.library.options(this);
2508 this.data_url += '|jsonwrap';
2510 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2511 this.data_url += '|nonzero';
2513 if(this.append_options !== null)
2514 this.data_url += '|' + this.append_options.toString();
2517 this.data_url += "&after=" + after.toString();
2520 this.data_url += "&before=" + before.toString();
2523 this.data_url += "&dimensions=" + this.dimensions;
2525 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2526 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2529 this.redrawChart = function() {
2530 if(this.data !== null)
2531 this.updateChartWithData(this.data);
2534 this.updateChartWithData = function(data) {
2535 if(this.debug === true)
2536 this.log('updateChartWithData() called.');
2538 this._updating = false;
2540 // this may force the chart to be re-created
2544 this.updates_counter++;
2545 this.updates_since_last_unhide++;
2546 this.updates_since_last_creation++;
2548 var started = new Date().getTime();
2550 // if the result is JSON, find the latest update-every
2551 this.data_update_every = data.view_update_every * 1000;
2552 this.data_after = data.after * 1000;
2553 this.data_before = data.before * 1000;
2554 this.netdata_first = data.first_entry * 1000;
2555 this.netdata_last = data.last_entry * 1000;
2556 this.data_points = data.points;
2559 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2560 if(this.view_after < this.data_after) {
2561 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2562 this.view_after = this.data_after;
2565 if(this.view_before > this.data_before) {
2566 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2567 this.view_before = this.data_before;
2571 this.view_after = this.data_after;
2572 this.view_before = this.data_before;
2575 if(this.debug === true) {
2576 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2578 if(this.current.force_after_ms)
2579 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2581 this.log('STATUS: forced : unset');
2583 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2584 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2585 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2586 this.log('STATUS: points : ' + (this.data_points).toString());
2589 if(this.data_points === 0) {
2594 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2595 if(this.debug === true)
2596 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2598 this.chart_created = false;
2601 // check and update the legend
2602 this.legendUpdateDOM();
2604 if(this.chart_created === true
2605 && typeof this.library.update === 'function') {
2607 if(this.debug === true)
2608 this.log('updating chart...');
2610 if(callChartLibraryUpdateSafely(data) === false)
2614 if(this.debug === true)
2615 this.log('creating chart...');
2617 if(callChartLibraryCreateSafely(data) === false)
2621 this.legendShowLatestValues();
2622 if(this.selected === true)
2623 NETDATA.globalSelectionSync.stop();
2625 // update the performance counters
2626 var now = new Date().getTime();
2627 this.tm.last_updated = now;
2629 // don't update last_autorefreshed if this chart is
2630 // forced to be updated with global PanAndZoom
2631 if(NETDATA.globalPanAndZoom.isActive())
2632 this.tm.last_autorefreshed = 0;
2634 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes)
2635 this.tm.last_autorefreshed = Math.round(now / this.data_update_every) * this.data_update_every;
2637 this.tm.last_autorefreshed = now;
2640 this.refresh_dt_ms = now - started;
2641 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2643 if(this.refresh_dt_element !== null)
2644 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2647 this.updateChart = function(callback) {
2648 if(this.debug === true)
2649 this.log('updateChart() called.');
2651 if(this._updating === true) {
2652 if(this.debug === true)
2653 this.log('I am already updating...');
2655 if(typeof callback === 'function') callback();
2659 // due to late initialization of charts and libraries
2660 // we need to check this too
2661 if(this.enabled === false) {
2662 if(this.debug === true)
2663 this.log('I am not enabled');
2665 if(typeof callback === 'function') callback();
2669 if(canBeRendered() === false) {
2670 if(typeof callback === 'function') callback();
2674 if(this.chart === null) {
2675 this.getChart(function() { that.updateChart(callback); });
2679 if(this.library.initialized === false) {
2680 if(this.library.enabled === true) {
2681 this.library.initialize(function() { that.updateChart(callback); });
2685 error('chart library "' + this.library_name + '" is not available.');
2686 if(typeof callback === 'function') callback();
2691 this.clearSelection();
2694 if(this.debug === true)
2695 this.log('updating from ' + this.data_url);
2697 this._updating = true;
2699 this.xhr = $.ajax( {
2701 crossDomain: NETDATA.options.crossDomainAjax,
2705 .success(function(data) {
2706 if(that.debug === true)
2707 that.log('data received. updating chart.');
2709 that.updateChartWithData(data);
2712 error('data download failed for url: ' + that.data_url);
2714 .always(function() {
2715 this._updating = false;
2716 if(typeof callback === 'function') callback();
2722 this.isVisible = function(nocache) {
2723 if(typeof nocache === 'undefined')
2726 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2728 // caching - we do not evaluate the charts visibility
2729 // if the page has not been scrolled since the last check
2730 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2731 return this.___isVisible___;
2733 this.tm.last_visible_check = new Date().getTime();
2735 var wh = window.innerHeight;
2736 var x = this.element.getBoundingClientRect();
2740 if(x.width === 0 || x.height === 0) {
2742 this.___isVisible___ = false;
2743 return this.___isVisible___;
2746 if(x.top < 0 && -x.top > x.height) {
2747 // the chart is entirely above
2748 ret = -x.top - x.height;
2750 else if(x.top > wh) {
2751 // the chart is entirely below
2755 if(ret > tolerance) {
2756 // the chart is too far
2759 this.___isVisible___ = false;
2760 return this.___isVisible___;
2763 // the chart is inside or very close
2766 this.___isVisible___ = true;
2767 return this.___isVisible___;
2771 this.isAutoRefreshed = function() {
2772 return (this.current.autorefresh);
2775 this.canBeAutoRefreshed = function() {
2776 now = new Date().getTime();
2778 if(this.enabled === false) {
2779 if(this.debug === true)
2780 this.log('I am not enabled');
2785 if(this.library === null || this.library.enabled === false) {
2786 error('charting library "' + this.library_name + '" is not available');
2787 if(this.debug === true)
2788 this.log('My chart library ' + this.library_name + ' is not available');
2793 if(this.isVisible() === false) {
2794 if(NETDATA.options.debug.visibility === true || this.debug === true)
2795 this.log('I am not visible');
2800 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2801 if(this.debug === true)
2802 this.log('timed force update detected - allowing this update');
2804 this.current.force_update_at = 0;
2808 if(this.isAutoRefreshed() === true) {
2809 // allow the first update, even if the page is not visible
2810 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2811 if(NETDATA.options.debug.focus === true || this.debug === true)
2812 this.log('canBeAutoRefreshed(): page does not have focus');
2817 if(this.needsRecreation() === true) {
2818 if(this.debug === true)
2819 this.log('canBeAutoRefreshed(): needs re-creation.');
2824 // options valid only for autoRefresh()
2825 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
2826 if(NETDATA.globalPanAndZoom.isActive()) {
2827 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
2828 if(this.debug === true)
2829 this.log('canBeAutoRefreshed(): global panning: I need an update.');
2834 if(this.debug === true)
2835 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
2841 if(this.selected === true) {
2842 if(this.debug === true)
2843 this.log('canBeAutoRefreshed(): I have a selection in place.');
2848 if(this.paused === true) {
2849 if(this.debug === true)
2850 this.log('canBeAutoRefreshed(): I am paused.');
2855 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
2856 if(this.debug === true)
2857 this.log('canBeAutoRefreshed(): It is time to update me.');
2867 this.autoRefresh = function(callback) {
2868 if(this.canBeAutoRefreshed() === true) {
2869 this.updateChart(callback);
2872 if(typeof callback !== 'undefined')
2877 this._defaultsFromDownloadedChart = function(chart) {
2879 this.chart_url = chart.url;
2880 this.data_update_every = chart.update_every * 1000;
2881 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2882 this.tm.last_info_downloaded = new Date().getTime();
2884 if(this.title === null)
2885 this.title = chart.title;
2887 if(this.units === null)
2888 this.units = chart.units;
2891 // fetch the chart description from the netdata server
2892 this.getChart = function(callback) {
2893 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
2895 this._defaultsFromDownloadedChart(this.chart);
2896 if(typeof callback === 'function') callback();
2899 this.chart_url = "/api/v1/chart?chart=" + this.id;
2901 if(this.debug === true)
2902 this.log('downloading ' + this.chart_url);
2905 url: this.host + this.chart_url,
2906 crossDomain: NETDATA.options.crossDomainAjax,
2910 .done(function(chart) {
2911 chart.url = that.chart_url;
2912 that._defaultsFromDownloadedChart(chart);
2913 NETDATA.chartRegistry.add(that.host, that.id, chart);
2916 NETDATA.error(404, that.chart_url);
2917 error('chart not found on url "' + that.chart_url + '"');
2919 .always(function() {
2920 if(typeof callback === 'function') callback();
2925 // ============================================================================================================
2931 NETDATA.resetAllCharts = function(state) {
2932 // first clear the global selection sync
2933 // to make sure no chart is in selected state
2934 state.globalSelectionSyncStop();
2936 // there are 2 possibilities here
2937 // a. state is the global Pan and Zoom master
2938 // b. state is not the global Pan and Zoom master
2940 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
2943 // clear the global Pan and Zoom
2944 // this will also refresh the master
2945 // and unblock any charts currently mirroring the master
2946 NETDATA.globalPanAndZoom.clearMaster();
2948 // if we were not the master, reset our status too
2949 // this is required because most probably the mouse
2950 // is over this chart, blocking it from autorefreshing
2951 if(master === false && (state.paused === true || state.selected === true))
2955 // get or create a chart state, given a DOM element
2956 NETDATA.chartState = function(element) {
2957 var state = $(element).data('netdata-state-object') || null;
2958 if(state === null) {
2959 state = new chartState(element);
2960 $(element).data('netdata-state-object', state);
2965 // ----------------------------------------------------------------------------------------------------------------
2966 // Library functions
2968 // Load a script without jquery
2969 // This is used to load jquery - after it is loaded, we use jquery
2970 NETDATA._loadjQuery = function(callback) {
2971 if(typeof jQuery === 'undefined') {
2972 if(NETDATA.options.debug.main_loop === true)
2973 console.log('loading ' + NETDATA.jQuery);
2975 var script = document.createElement('script');
2976 script.type = 'text/javascript';
2977 script.async = true;
2978 script.src = NETDATA.jQuery;
2980 // script.onabort = onError;
2981 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
2982 if(typeof callback === "function")
2983 script.onload = callback;
2985 var s = document.getElementsByTagName('script')[0];
2986 s.parentNode.insertBefore(script, s);
2988 else if(typeof callback === "function")
2992 NETDATA._loadCSS = function(filename) {
2993 // don't use jQuery here
2994 // styles are loaded before jQuery
2995 // to eliminate showing an unstyled page to the user
2997 var fileref = document.createElement("link");
2998 fileref.setAttribute("rel", "stylesheet");
2999 fileref.setAttribute("type", "text/css");
3000 fileref.setAttribute("href", filename);
3002 if (typeof fileref !== 'undefined')
3003 document.getElementsByTagName("head")[0].appendChild(fileref);
3006 NETDATA.colorHex2Rgb = function(hex) {
3007 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3008 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3009 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3010 return r + r + g + g + b + b;
3013 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3015 r: parseInt(result[1], 16),
3016 g: parseInt(result[2], 16),
3017 b: parseInt(result[3], 16)
3021 NETDATA.colorLuminance = function(hex, lum) {
3022 // validate hex string
3023 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3025 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3029 // convert to decimal and change luminosity
3030 var rgb = "#", c, i;
3031 for (i = 0; i < 3; i++) {
3032 c = parseInt(hex.substr(i*2,2), 16);
3033 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3034 rgb += ("00"+c).substr(c.length);
3040 NETDATA.guid = function() {
3042 return Math.floor((1 + Math.random()) * 0x10000)
3047 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3050 NETDATA.zeropad = function(x) {
3051 if(x > -10 && x < 10) return '0' + x.toString();
3052 else return x.toString();
3055 // user function to signal us the DOM has been
3057 NETDATA.updatedDom = function() {
3058 NETDATA.options.updated_dom = true;
3061 NETDATA.ready = function(callback) {
3062 NETDATA.options.pauseCallback = callback;
3065 NETDATA.pause = function(callback) {
3066 if(NETDATA.options.pause === true)
3069 NETDATA.options.pauseCallback = callback;
3072 NETDATA.unpause = function() {
3073 NETDATA.options.pauseCallback = null;
3074 NETDATA.options.updated_dom = true;
3075 NETDATA.options.pause = false;
3078 // ----------------------------------------------------------------------------------------------------------------
3080 // this is purely sequencial charts refresher
3081 // it is meant to be autonomous
3082 NETDATA.chartRefresherNoParallel = function(index) {
3083 if(NETDATA.options.debug.mail_loop === true)
3084 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3086 if(NETDATA.options.updated_dom === true) {
3087 // the dom has been updated
3088 // get the dom parts again
3089 NETDATA.parseDom(NETDATA.chartRefresher);
3092 if(index >= NETDATA.options.targets.length) {
3093 if(NETDATA.options.debug.main_loop === true)
3094 console.log('waiting to restart main loop...');
3096 NETDATA.options.auto_refresher_fast_weight = 0;
3098 setTimeout(function() {
3099 NETDATA.chartRefresher();
3100 }, NETDATA.options.current.idle_between_loops);
3103 var state = NETDATA.options.targets[index];
3105 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3106 if(NETDATA.options.debug.main_loop === true)
3107 console.log('fast rendering...');
3109 state.autoRefresh(function() {
3110 NETDATA.chartRefresherNoParallel(++index);
3114 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3115 NETDATA.options.auto_refresher_fast_weight = 0;
3117 setTimeout(function() {
3118 state.autoRefresh(function() {
3119 NETDATA.chartRefresherNoParallel(++index);
3121 }, NETDATA.options.current.idle_between_charts);
3126 // this is part of the parallel refresher
3127 // its cause is to refresh sequencially all the charts
3128 // that depend on chart library initialization
3129 // it will call the parallel refresher back
3130 // as soon as it sees a chart that its chart library
3132 NETDATA.chartRefresher_unitialized = function() {
3133 if(NETDATA.options.updated_dom === true) {
3134 // the dom has been updated
3135 // get the dom parts again
3136 NETDATA.parseDom(NETDATA.chartRefresher);
3140 if(NETDATA.options.sequencial.length === 0)
3141 NETDATA.chartRefresher();
3143 var state = NETDATA.options.sequencial.pop();
3144 if(state.library.initialized === true)
3145 NETDATA.chartRefresher();
3147 state.autoRefresh(NETDATA.chartRefresher_unitialized);
3151 NETDATA.chartRefresherWaitTime = function() {
3152 return NETDATA.options.current.idle_parallel_loops;
3155 // the default refresher
3156 // it will create 2 sets of charts:
3157 // - the ones that can be refreshed in parallel
3158 // - the ones that depend on something else
3159 // the first set will be executed in parallel
3160 // the second will be given to NETDATA.chartRefresher_unitialized()
3161 NETDATA.chartRefresher = function() {
3162 if(NETDATA.options.pause === true) {
3163 // console.log('auto-refresher is paused');
3164 setTimeout(NETDATA.chartRefresher,
3165 NETDATA.chartRefresherWaitTime());
3169 if(typeof NETDATA.options.pauseCallback === 'function') {
3170 // console.log('auto-refresher is calling pauseCallback');
3171 NETDATA.options.pause = true;
3172 NETDATA.options.pauseCallback();
3173 NETDATA.chartRefresher();
3177 if(NETDATA.options.current.parallel_refresher === false) {
3178 NETDATA.chartRefresherNoParallel(0);
3182 if(NETDATA.options.updated_dom === true) {
3183 // the dom has been updated
3184 // get the dom parts again
3185 NETDATA.parseDom(NETDATA.chartRefresher);
3189 var parallel = new Array();
3190 var targets = NETDATA.options.targets;
3191 var len = targets.length;
3193 if(targets[len].isVisible() === false)
3196 var state = targets[len];
3197 if(state.library.initialized === false) {
3198 if(state.library.enabled === true) {
3199 state.library.initialize(NETDATA.chartRefresher);
3203 state.error('chart library "' + state.library_name + '" is not enabled.');
3207 parallel.unshift(state);
3210 if(parallel.length > 0) {
3211 var parallel_jobs = parallel.length;
3213 // this will execute the jobs in parallel
3214 $(parallel).each(function() {
3215 this.autoRefresh(function() {
3218 if(parallel_jobs === 0) {
3219 setTimeout(NETDATA.chartRefresher,
3220 NETDATA.chartRefresherWaitTime());
3226 setTimeout(NETDATA.chartRefresher,
3227 NETDATA.chartRefresherWaitTime());
3231 NETDATA.parseDom = function(callback) {
3232 NETDATA.options.last_page_scroll = new Date().getTime();
3233 NETDATA.options.updated_dom = false;
3235 var targets = $('div[data-netdata]'); //.filter(':visible');
3237 if(NETDATA.options.debug.main_loop === true)
3238 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3240 NETDATA.options.targets = new Array();
3241 var len = targets.length;
3243 // the initialization will take care of sizing
3244 // and the "loading..." message
3245 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3248 if(typeof callback === 'function') callback();
3251 // this is the main function - where everything starts
3252 NETDATA.start = function() {
3253 // this should be called only once
3255 NETDATA.options.page_is_visible = true;
3257 $(window).blur(function() {
3258 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3259 NETDATA.options.page_is_visible = false;
3260 if(NETDATA.options.debug.focus === true)
3261 console.log('Lost Focus!');
3265 $(window).focus(function() {
3266 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3267 NETDATA.options.page_is_visible = true;
3268 if(NETDATA.options.debug.focus === true)
3269 console.log('Focus restored!');
3273 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3274 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3275 NETDATA.options.page_is_visible = false;
3276 if(NETDATA.options.debug.focus === true)
3277 console.log('Document has no focus!');
3281 // bootstrap tab switching
3282 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3284 // bootstrap modal switching
3285 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3286 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3288 NETDATA.parseDom(NETDATA.chartRefresher);
3291 // ----------------------------------------------------------------------------------------------------------------
3294 NETDATA.peityInitialize = function(callback) {
3295 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3297 url: NETDATA.peity_js,
3302 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3305 NETDATA.chartLibraries.peity.enabled = false;
3306 NETDATA.error(100, NETDATA.peity_js);
3308 .always(function() {
3309 if(typeof callback === "function")
3314 NETDATA.chartLibraries.peity.enabled = false;
3315 if(typeof callback === "function")
3320 NETDATA.peityChartUpdate = function(state, data) {
3321 state.peity_instance.innerHTML = data.result;
3323 if(state.peity_options.stroke !== state.chartColors()[0]) {
3324 state.peity_options.stroke = state.chartColors()[0];
3325 if(state.chart.chart_type === 'line')
3326 state.peity_options.fill = NETDATA.themes.current.background;
3328 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3331 $(state.peity_instance).peity('line', state.peity_options);
3335 NETDATA.peityChartCreate = function(state, data) {
3336 state.peity_instance = document.createElement('div');
3337 state.element_chart.appendChild(state.peity_instance);
3339 var self = $(state.element);
3340 state.peity_options = {
3341 stroke: NETDATA.themes.current.foreground,
3342 strokeWidth: self.data('peity-strokewidth') || 1,
3343 width: state.chartWidth(),
3344 height: state.chartHeight(),
3345 fill: NETDATA.themes.current.foreground
3348 NETDATA.peityChartUpdate(state, data);
3352 // ----------------------------------------------------------------------------------------------------------------
3355 NETDATA.sparklineInitialize = function(callback) {
3356 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3358 url: NETDATA.sparkline_js,
3363 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3366 NETDATA.chartLibraries.sparkline.enabled = false;
3367 NETDATA.error(100, NETDATA.sparkline_js);
3369 .always(function() {
3370 if(typeof callback === "function")
3375 NETDATA.chartLibraries.sparkline.enabled = false;
3376 if(typeof callback === "function")
3381 NETDATA.sparklineChartUpdate = function(state, data) {
3382 state.sparkline_options.width = state.chartWidth();
3383 state.sparkline_options.height = state.chartHeight();
3385 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3389 NETDATA.sparklineChartCreate = function(state, data) {
3390 var self = $(state.element);
3391 var type = self.data('sparkline-type') || 'line';
3392 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3393 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3394 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3395 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3396 var composite = self.data('sparkline-composite') || undefined;
3397 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3398 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3399 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3400 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3401 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3402 var spotColor = self.data('sparkline-spotcolor') || undefined;
3403 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3404 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3405 var spotRadius = self.data('sparkline-spotradius') || undefined;
3406 var valueSpots = self.data('sparkline-valuespots') || undefined;
3407 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3408 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3409 var lineWidth = self.data('sparkline-linewidth') || undefined;
3410 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3411 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3412 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3413 var xvalues = self.data('sparkline-xvalues') || undefined;
3414 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3415 var xvalues = self.data('sparkline-xvalues') || undefined;
3416 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3417 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3418 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3419 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3420 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3421 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3422 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3423 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3424 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3425 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3426 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3427 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3428 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3429 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3430 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3431 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3432 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3433 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3434 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3435 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3436 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3438 state.sparkline_options = {
3440 lineColor: lineColor,
3441 fillColor: fillColor,
3442 chartRangeMin: chartRangeMin,
3443 chartRangeMax: chartRangeMax,
3444 composite: composite,
3445 enableTagOptions: enableTagOptions,
3446 tagOptionPrefix: tagOptionPrefix,
3447 tagValuesAttribute: tagValuesAttribute,
3448 disableHiddenCheck: disableHiddenCheck,
3449 defaultPixelsPerValue: defaultPixelsPerValue,
3450 spotColor: spotColor,
3451 minSpotColor: minSpotColor,
3452 maxSpotColor: maxSpotColor,
3453 spotRadius: spotRadius,
3454 valueSpots: valueSpots,
3455 highlightSpotColor: highlightSpotColor,
3456 highlightLineColor: highlightLineColor,
3457 lineWidth: lineWidth,
3458 normalRangeMin: normalRangeMin,
3459 normalRangeMax: normalRangeMax,
3460 drawNormalOnTop: drawNormalOnTop,
3462 chartRangeClip: chartRangeClip,
3463 chartRangeMinX: chartRangeMinX,
3464 chartRangeMaxX: chartRangeMaxX,
3465 disableInteraction: disableInteraction,
3466 disableTooltips: disableTooltips,
3467 disableHighlight: disableHighlight,
3468 highlightLighten: highlightLighten,
3469 highlightColor: highlightColor,
3470 tooltipContainer: tooltipContainer,
3471 tooltipClassname: tooltipClassname,
3472 tooltipChartTitle: state.title,
3473 tooltipFormat: tooltipFormat,
3474 tooltipPrefix: tooltipPrefix,
3475 tooltipSuffix: tooltipSuffix,
3476 tooltipSkipNull: tooltipSkipNull,
3477 tooltipValueLookups: tooltipValueLookups,
3478 tooltipFormatFieldlist: tooltipFormatFieldlist,
3479 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3480 numberFormatter: numberFormatter,
3481 numberDigitGroupSep: numberDigitGroupSep,
3482 numberDecimalMark: numberDecimalMark,
3483 numberDigitGroupCount: numberDigitGroupCount,
3484 animatedZooms: animatedZooms,
3485 width: state.chartWidth(),
3486 height: state.chartHeight()
3489 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3493 // ----------------------------------------------------------------------------------------------------------------
3500 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3501 if(after < state.netdata_first)
3502 after = state.netdata_first;
3504 if(before > state.netdata_last)
3505 before = state.netdata_last;
3507 state.setMode('zoom');
3508 state.globalSelectionSyncStop();
3509 state.globalSelectionSyncDelay();
3510 state.dygraph_user_action = true;
3511 state.dygraph_force_zoom = true;
3512 state.updateChartPanOrZoom(after, before);
3513 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3516 NETDATA.dygraphSetSelection = function(state, t) {
3517 if(typeof state.dygraph_instance !== 'undefined') {
3518 var r = state.calculateRowForTime(t);
3520 state.dygraph_instance.setSelection(r);
3522 state.dygraph_instance.clearSelection();
3523 state.legendShowUndefined();
3530 NETDATA.dygraphClearSelection = function(state, t) {
3531 if(typeof state.dygraph_instance !== 'undefined') {
3532 state.dygraph_instance.clearSelection();
3537 NETDATA.dygraphSmoothInitialize = function(callback) {
3539 url: NETDATA.dygraph_smooth_js,
3544 NETDATA.dygraph.smooth = true;
3545 smoothPlotter.smoothing = 0.3;
3548 NETDATA.dygraph.smooth = false;
3550 .always(function() {
3551 if(typeof callback === "function")
3556 NETDATA.dygraphInitialize = function(callback) {
3557 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3559 url: NETDATA.dygraph_js,
3564 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3567 NETDATA.chartLibraries.dygraph.enabled = false;
3568 NETDATA.error(100, NETDATA.dygraph_js);
3570 .always(function() {
3571 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3572 NETDATA.dygraphSmoothInitialize(callback);
3573 else if(typeof callback === "function")
3578 NETDATA.chartLibraries.dygraph.enabled = false;
3579 if(typeof callback === "function")
3584 NETDATA.dygraphChartUpdate = function(state, data) {
3585 var dygraph = state.dygraph_instance;
3587 if(typeof dygraph === 'undefined')
3588 return NETDATA.dygraphChartCreate(state, data);
3590 // when the chart is not visible, and hidden
3591 // if there is a window resize, dygraph detects
3592 // its element size as 0x0.
3593 // this will make it re-appear properly
3595 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3599 file: data.result.data,
3600 colors: state.chartColors(),
3601 labels: data.result.labels,
3602 labelsDivWidth: state.chartWidth() - 70,
3603 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3606 if(state.dygraph_force_zoom === true) {
3607 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3608 state.log('dygraphChartUpdate() forced zoom update');
3610 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3611 options.valueRange = null;
3612 options.isZoomedIgnoreProgrammaticZoom = true;
3613 state.dygraph_force_zoom = false;
3615 else if(state.current.name !== 'auto') {
3616 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3617 state.log('dygraphChartUpdate() loose update');
3620 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3621 state.log('dygraphChartUpdate() strict update');
3623 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3624 options.valueRange = null;
3625 options.isZoomedIgnoreProgrammaticZoom = true;
3628 if(state.dygraph_smooth_eligible === true) {
3629 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3630 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3631 NETDATA.dygraphChartCreate(state, data);
3636 dygraph.updateOptions(options);
3638 state.dygraph_last_rendered = new Date().getTime();
3642 NETDATA.dygraphChartCreate = function(state, data) {
3643 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3644 state.log('dygraphChartCreate()');
3646 var self = $(state.element);
3648 var chart_type = state.chart.chart_type;
3649 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3650 chart_type = self.data('dygraph-type') || chart_type;
3652 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3653 smooth = self.data('dygraph-smooth') || smooth;
3655 if(NETDATA.dygraph.smooth === false)
3658 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3659 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3661 state.dygraph_options = {
3662 colors: self.data('dygraph-colors') || state.chartColors(),
3664 // leave a few pixels empty on the right of the chart
3665 rightGap: self.data('dygraph-rightgap') || 5,
3666 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3667 showRoller: self.data('dygraph-showroller') || false,
3669 title: self.data('dygraph-title') || state.title,
3670 titleHeight: self.data('dygraph-titleheight') || 19,
3672 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3673 labels: data.result.labels,
3674 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3675 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3676 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3677 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3678 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3681 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3682 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3684 ylabel: state.units,
3685 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3687 // the function to plot the chart
3690 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3691 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3692 strokePattern: self.data('dygraph-strokepattern') || undefined,
3694 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3695 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3696 drawPoints: self.data('dygraph-drawpoints') || false,
3698 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3699 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3701 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3702 pointSize: self.data('dygraph-pointsize') || 1,
3704 // enabling this makes the chart with little square lines
3705 stepPlot: self.data('dygraph-stepplot') || false,
3707 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3708 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3709 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3711 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3712 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3713 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3714 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3716 drawAxis: self.data('dygraph-drawaxis') || true,
3717 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3718 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3719 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3721 drawGrid: self.data('dygraph-drawgrid') || true,
3722 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3723 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3724 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3725 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3726 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3728 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3729 sigFigs: self.data('dygraph-sigfigs') || null,
3730 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3731 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3733 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3734 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3735 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3737 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3738 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3742 ticker: Dygraph.dateTicker,
3743 axisLabelFormatter: function (d, gran) {
3744 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3746 valueFormatter: function (ms) {
3747 var d = new Date(ms);
3748 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3749 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3754 valueFormatter: function (x) {
3755 // we format legends with the state object
3756 // no need to do anything here
3757 // return (Math.round(x*100) / 100).toLocaleString();
3758 // return state.legendFormatValue(x);
3763 legendFormatter: function(data) {
3764 var elements = state.element_legend_childs;
3766 // if the hidden div is not there
3767 // we are not managing the legend
3768 if(elements.hidden === null) return;
3770 if (typeof data.x !== 'undefined') {
3771 state.legendSetDate(data.x);
3772 var i = data.series.length;
3774 var series = data.series[i];
3775 if(!series.isVisible) continue;
3776 state.legendSetLabelValue(series.label, series.y);
3782 drawCallback: function(dygraph, is_initial) {
3783 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3784 state.dygraph_user_action = false;
3786 var x_range = dygraph.xAxisRange();
3787 var after = Math.round(x_range[0]);
3788 var before = Math.round(x_range[1]);
3790 if(NETDATA.options.debug.dygraph === true)
3791 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3793 if(before <= state.netdata_last && after >= state.netdata_first)
3794 state.updateChartPanOrZoom(after, before);
3797 zoomCallback: function(minDate, maxDate, yRanges) {
3798 if(NETDATA.options.debug.dygraph === true)
3799 state.log('dygraphZoomCallback()');
3801 state.globalSelectionSyncStop();
3802 state.globalSelectionSyncDelay();
3803 state.setMode('zoom');
3805 // refresh it to the greatest possible zoom level
3806 state.dygraph_user_action = true;
3807 state.dygraph_force_zoom = true;
3808 state.updateChartPanOrZoom(minDate, maxDate);
3810 highlightCallback: function(event, x, points, row, seriesName) {
3811 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3812 state.log('dygraphHighlightCallback()');
3816 // there is a bug in dygraph when the chart is zoomed enough
3817 // the time it thinks is selected is wrong
3818 // here we calculate the time t based on the row number selected
3820 var t = state.data_after + row * state.data_update_every;
3821 // 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);
3823 state.globalSelectionSync(x);
3825 // fix legend zIndex using the internal structures of dygraph legend module
3826 // this works, but it is a hack!
3827 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
3829 unhighlightCallback: function(event) {
3830 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3831 state.log('dygraphUnhighlightCallback()');
3833 state.unpauseChart();
3834 state.globalSelectionSyncStop();
3836 interactionModel : {
3837 mousedown: function(event, dygraph, context) {
3838 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3839 state.log('interactionModel.mousedown()');
3841 state.dygraph_user_action = true;
3842 state.globalSelectionSyncStop();
3844 if(NETDATA.options.debug.dygraph === true)
3845 state.log('dygraphMouseDown()');
3847 // Right-click should not initiate a zoom.
3848 if(event.button && event.button === 2) return;
3850 context.initializeMouseDown(event, dygraph, context);
3852 if(event.button && event.button === 1) {
3853 if (event.altKey || event.shiftKey) {
3854 state.setMode('pan');
3855 state.globalSelectionSyncDelay();
3856 Dygraph.startPan(event, dygraph, context);
3859 state.setMode('zoom');
3860 state.globalSelectionSyncDelay();
3861 Dygraph.startZoom(event, dygraph, context);
3865 if (event.altKey || event.shiftKey) {
3866 state.setMode('zoom');
3867 state.globalSelectionSyncDelay();
3868 Dygraph.startZoom(event, dygraph, context);
3871 state.setMode('pan');
3872 state.globalSelectionSyncDelay();
3873 Dygraph.startPan(event, dygraph, context);
3877 mousemove: function(event, dygraph, context) {
3878 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3879 state.log('interactionModel.mousemove()');
3881 if(context.isPanning) {
3882 state.dygraph_user_action = true;
3883 state.globalSelectionSyncStop();
3884 state.globalSelectionSyncDelay();
3885 state.setMode('pan');
3886 Dygraph.movePan(event, dygraph, context);
3888 else if(context.isZooming) {
3889 state.dygraph_user_action = true;
3890 state.globalSelectionSyncStop();
3891 state.globalSelectionSyncDelay();
3892 state.setMode('zoom');
3893 Dygraph.moveZoom(event, dygraph, context);
3896 mouseup: function(event, dygraph, context) {
3897 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3898 state.log('interactionModel.mouseup()');
3900 if (context.isPanning) {
3901 state.dygraph_user_action = true;
3902 state.globalSelectionSyncDelay();
3903 Dygraph.endPan(event, dygraph, context);
3905 else if (context.isZooming) {
3906 state.dygraph_user_action = true;
3907 state.globalSelectionSyncDelay();
3908 Dygraph.endZoom(event, dygraph, context);
3911 click: function(event, dygraph, context) {
3912 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3913 state.log('interactionModel.click()');
3915 event.preventDefault();
3917 dblclick: function(event, dygraph, context) {
3918 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3919 state.log('interactionModel.dblclick()');
3920 NETDATA.resetAllCharts(state);
3922 mousewheel: function(event, dygraph, context) {
3923 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3924 state.log('interactionModel.mousewheel()');
3926 // Take the offset of a mouse event on the dygraph canvas and
3927 // convert it to a pair of percentages from the bottom left.
3928 // (Not top left, bottom is where the lower value is.)
3929 function offsetToPercentage(g, offsetX, offsetY) {
3930 // This is calculating the pixel offset of the leftmost date.
3931 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
3932 var yar0 = g.yAxisRange(0);
3934 // This is calculating the pixel of the higest value. (Top pixel)
3935 var yOffset = g.toDomCoords(null, yar0[1])[1];
3937 // x y w and h are relative to the corner of the drawing area,
3938 // so that the upper corner of the drawing area is (0, 0).
3939 var x = offsetX - xOffset;
3940 var y = offsetY - yOffset;
3942 // This is computing the rightmost pixel, effectively defining the
3944 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
3946 // This is computing the lowest pixel, effectively defining the height.
3947 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
3949 // Percentage from the left.
3950 var xPct = w === 0 ? 0 : (x / w);
3951 // Percentage from the top.
3952 var yPct = h === 0 ? 0 : (y / h);
3954 // The (1-) part below changes it from "% distance down from the top"
3955 // to "% distance up from the bottom".
3956 return [xPct, (1-yPct)];
3959 // Adjusts [x, y] toward each other by zoomInPercentage%
3960 // Split it so the left/bottom axis gets xBias/yBias of that change and
3961 // tight/top gets (1-xBias)/(1-yBias) of that change.
3963 // If a bias is missing it splits it down the middle.
3964 function zoomRange(g, zoomInPercentage, xBias, yBias) {
3965 xBias = xBias || 0.5;
3966 yBias = yBias || 0.5;
3968 function adjustAxis(axis, zoomInPercentage, bias) {
3969 var delta = axis[1] - axis[0];
3970 var increment = delta * zoomInPercentage;
3971 var foo = [increment * bias, increment * (1-bias)];
3973 return [ axis[0] + foo[0], axis[1] - foo[1] ];
3976 var yAxes = g.yAxisRanges();
3978 for (var i = 0; i < yAxes.length; i++) {
3979 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
3982 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
3985 if(event.altKey || event.shiftKey) {
3986 state.dygraph_user_action = true;
3988 state.globalSelectionSyncStop();
3989 state.globalSelectionSyncDelay();
3991 // http://dygraphs.com/gallery/interaction-api.js
3992 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
3993 var percentage = normal / 50;
3995 if (!(event.offsetX && event.offsetY)){
3996 event.offsetX = event.layerX - event.target.offsetLeft;
3997 event.offsetY = event.layerY - event.target.offsetTop;
4000 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4001 var xPct = percentages[0];
4002 var yPct = percentages[1];
4004 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4006 var after = new_x_range[0];
4007 var before = new_x_range[1];
4009 var first = state.netdata_first + state.data_update_every;
4010 var last = state.netdata_last + state.data_update_every;
4013 after -= (before - last);
4020 state.setMode('zoom');
4021 if(state.updateChartPanOrZoom(after, before) === true)
4022 dygraph.updateOptions({ dateWindow: [ after, before ] });
4024 event.preventDefault();
4027 touchstart: function(event, dygraph, context) {
4028 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4029 state.log('interactionModel.touchstart()');
4031 state.dygraph_user_action = true;
4032 state.setMode('zoom');
4035 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4037 // we overwrite the touch directions at the end, to overwrite
4038 // the internal default of dygraphs
4039 context.touchDirections = { x: true, y: false };
4041 state.dygraph_last_touch_start = new Date().getTime();
4042 state.dygraph_last_touch_move = 0;
4044 if(typeof event.touches[0].pageX === 'number')
4045 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4047 state.dygraph_last_touch_page_x = 0;
4049 touchmove: function(event, dygraph, context) {
4050 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4051 state.log('interactionModel.touchmove()');
4053 state.dygraph_user_action = true;
4054 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4056 state.dygraph_last_touch_move = new Date().getTime();
4058 touchend: function(event, dygraph, context) {
4059 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4060 state.log('interactionModel.touchend()');
4062 state.dygraph_user_action = true;
4063 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4065 // if it didn't move, it is a selection
4066 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4067 // internal api of dygraphs
4068 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4069 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4070 if(NETDATA.dygraphSetSelection(state, t) === true)
4071 state.globalSelectionSync(t);
4074 // if it was double tap within double click time, reset the charts
4075 var now = new Date().getTime();
4076 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4077 if(state.dygraph_last_touch_move === 0) {
4078 var dt = now - state.dygraph_last_touch_end;
4079 if(dt <= NETDATA.options.current.double_click_speed)
4080 NETDATA.resetAllCharts(state);
4084 // remember the timestamp of the last touch end
4085 state.dygraph_last_touch_end = now;
4090 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4091 state.dygraph_options.drawGrid = false;
4092 state.dygraph_options.drawAxis = false;
4093 state.dygraph_options.title = undefined;
4094 state.dygraph_options.units = undefined;
4095 state.dygraph_options.ylabel = undefined;
4096 state.dygraph_options.yLabelWidth = 0;
4097 state.dygraph_options.labelsDivWidth = 120;
4098 state.dygraph_options.labelsDivStyles.width = '120px';
4099 state.dygraph_options.labelsSeparateLines = true;
4100 state.dygraph_options.rightGap = 0;
4103 if(smooth === true) {
4104 state.dygraph_smooth_eligible = true;
4106 if(NETDATA.options.current.smooth_plot === true)
4107 state.dygraph_options.plotter = smoothPlotter;
4109 else state.dygraph_smooth_eligible = false;
4111 state.dygraph_instance = new Dygraph(state.element_chart,
4112 data.result.data, state.dygraph_options);
4114 state.dygraph_force_zoom = false;
4115 state.dygraph_user_action = false;
4116 state.dygraph_last_rendered = new Date().getTime();
4120 // ----------------------------------------------------------------------------------------------------------------
4123 NETDATA.morrisInitialize = function(callback) {
4124 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4126 // morris requires raphael
4127 if(!NETDATA.chartLibraries.raphael.initialized) {
4128 if(NETDATA.chartLibraries.raphael.enabled) {
4129 NETDATA.raphaelInitialize(function() {
4130 NETDATA.morrisInitialize(callback);
4134 NETDATA.chartLibraries.morris.enabled = false;
4135 if(typeof callback === "function")
4140 NETDATA._loadCSS(NETDATA.morris_css);
4143 url: NETDATA.morris_js,
4148 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4151 NETDATA.chartLibraries.morris.enabled = false;
4152 NETDATA.error(100, NETDATA.morris_js);
4154 .always(function() {
4155 if(typeof callback === "function")
4161 NETDATA.chartLibraries.morris.enabled = false;
4162 if(typeof callback === "function")
4167 NETDATA.morrisChartUpdate = function(state, data) {
4168 state.morris_instance.setData(data.result.data);
4172 NETDATA.morrisChartCreate = function(state, data) {
4174 state.morris_options = {
4175 element: state.element_chart.id,
4176 data: data.result.data,
4178 ykeys: data.dimension_names,
4179 labels: data.dimension_names,
4185 continuousLine: false,
4186 behaveLikeLine: false
4189 if(state.chart.chart_type === 'line')
4190 state.morris_instance = new Morris.Line(state.morris_options);
4192 else if(state.chart.chart_type === 'area') {
4193 state.morris_options.behaveLikeLine = true;
4194 state.morris_instance = new Morris.Area(state.morris_options);
4197 state.morris_instance = new Morris.Area(state.morris_options);
4202 // ----------------------------------------------------------------------------------------------------------------
4205 NETDATA.raphaelInitialize = function(callback) {
4206 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4208 url: NETDATA.raphael_js,
4213 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4216 NETDATA.chartLibraries.raphael.enabled = false;
4217 NETDATA.error(100, NETDATA.raphael_js);
4219 .always(function() {
4220 if(typeof callback === "function")
4225 NETDATA.chartLibraries.raphael.enabled = false;
4226 if(typeof callback === "function")
4231 NETDATA.raphaelChartUpdate = function(state, data) {
4232 $(state.element_chart).raphael(data.result, {
4233 width: state.chartWidth(),
4234 height: state.chartHeight()
4240 NETDATA.raphaelChartCreate = function(state, data) {
4241 $(state.element_chart).raphael(data.result, {
4242 width: state.chartWidth(),
4243 height: state.chartHeight()
4249 // ----------------------------------------------------------------------------------------------------------------
4252 NETDATA.c3Initialize = function(callback) {
4253 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4256 if(!NETDATA.chartLibraries.d3.initialized) {
4257 if(NETDATA.chartLibraries.d3.enabled) {
4258 NETDATA.d3Initialize(function() {
4259 NETDATA.c3Initialize(callback);
4263 NETDATA.chartLibraries.c3.enabled = false;
4264 if(typeof callback === "function")
4269 NETDATA._loadCSS(NETDATA.c3_css);
4277 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4280 NETDATA.chartLibraries.c3.enabled = false;
4281 NETDATA.error(100, NETDATA.c3_js);
4283 .always(function() {
4284 if(typeof callback === "function")
4290 NETDATA.chartLibraries.c3.enabled = false;
4291 if(typeof callback === "function")
4296 NETDATA.c3ChartUpdate = function(state, data) {
4297 state.c3_instance.destroy();
4298 return NETDATA.c3ChartCreate(state, data);
4300 //state.c3_instance.load({
4301 // rows: data.result,
4308 NETDATA.c3ChartCreate = function(state, data) {
4310 state.element_chart.id = 'c3-' + state.uuid;
4311 // console.log('id = ' + state.element_chart.id);
4313 state.c3_instance = c3.generate({
4314 bindto: '#' + state.element_chart.id,
4316 width: state.chartWidth(),
4317 height: state.chartHeight()
4320 pattern: state.chartColors()
4325 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4331 format: function(x) {
4332 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4359 // console.log(state.c3_instance);
4364 // ----------------------------------------------------------------------------------------------------------------
4367 NETDATA.d3Initialize = function(callback) {
4368 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4375 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4378 NETDATA.chartLibraries.d3.enabled = false;
4379 NETDATA.error(100, NETDATA.d3_js);
4381 .always(function() {
4382 if(typeof callback === "function")
4387 NETDATA.chartLibraries.d3.enabled = false;
4388 if(typeof callback === "function")
4393 NETDATA.d3ChartUpdate = function(state, data) {
4397 NETDATA.d3ChartCreate = function(state, data) {
4401 // ----------------------------------------------------------------------------------------------------------------
4404 NETDATA.googleInitialize = function(callback) {
4405 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4407 url: NETDATA.google_js,
4412 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4413 google.load('visualization', '1.1', {
4414 'packages': ['corechart', 'controls'],
4415 'callback': callback
4419 NETDATA.chartLibraries.google.enabled = false;
4420 NETDATA.error(100, NETDATA.google_js);
4421 if(typeof callback === "function")
4426 NETDATA.chartLibraries.google.enabled = false;
4427 if(typeof callback === "function")
4432 NETDATA.googleChartUpdate = function(state, data) {
4433 var datatable = new google.visualization.DataTable(data.result);
4434 state.google_instance.draw(datatable, state.google_options);
4438 NETDATA.googleChartCreate = function(state, data) {
4439 var datatable = new google.visualization.DataTable(data.result);
4441 state.google_options = {
4442 colors: state.chartColors(),
4444 // do not set width, height - the chart resizes itself
4445 //width: state.chartWidth(),
4446 //height: state.chartHeight(),
4451 // title: "Time of Day",
4452 // format:'HH:mm:ss',
4453 viewWindowMode: 'maximized',
4465 viewWindowMode: 'pretty',
4480 focusTarget: 'category',
4487 titlePosition: 'out',
4498 curveType: 'function',
4503 switch(state.chart.chart_type) {
4505 state.google_options.vAxis.viewWindowMode = 'maximized';
4506 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4507 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4511 state.google_options.isStacked = true;
4512 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4513 state.google_options.vAxis.viewWindowMode = 'maximized';
4514 state.google_options.vAxis.minValue = null;
4515 state.google_options.vAxis.maxValue = null;
4516 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4521 state.google_options.lineWidth = 2;
4522 state.google_instance = new google.visualization.LineChart(state.element_chart);
4526 state.google_instance.draw(datatable, state.google_options);
4530 // ----------------------------------------------------------------------------------------------------------------
4532 NETDATA.percentFromValueMax = function(value, max) {
4533 if(value === null) value = 0;
4534 if(max < value) max = value;
4538 pcent = Math.round(value * 100 / max);
4539 if(pcent === 0 && value > 0) pcent = 1;
4545 // ----------------------------------------------------------------------------------------------------------------
4548 NETDATA.easypiechartInitialize = function(callback) {
4549 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4551 url: NETDATA.easypiechart_js,
4556 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4559 NETDATA.chartLibraries.easypiechart.enabled = false;
4560 NETDATA.error(100, NETDATA.easypiechart_js);
4562 .always(function() {
4563 if(typeof callback === "function")
4568 NETDATA.chartLibraries.easypiechart.enabled = false;
4569 if(typeof callback === "function")
4574 NETDATA.easypiechartClearSelection = function(state) {
4575 if(typeof state.easyPieChartEvent !== 'undefined') {
4576 if(state.easyPieChartEvent.timer !== null)
4577 clearTimeout(state.easyPieChartEvent.timer);
4579 state.easyPieChartEvent.timer = null;
4582 if(state.isAutoRefreshed() === true && state.data !== null) {
4583 NETDATA.easypiechartChartUpdate(state, state.data);
4586 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4587 state.easyPieChart_instance.update(0);
4589 state.easyPieChart_instance.enableAnimation();
4594 NETDATA.easypiechartSetSelection = function(state, t) {
4595 if(state.timeIsVisible(t) !== true)
4596 return NETDATA.easypiechartClearSelection(state);
4598 var slot = state.calculateRowForTime(t);
4599 if(slot < 0 || slot >= state.data.result.length)
4600 return NETDATA.easypiechartClearSelection(state);
4602 if(typeof state.easyPieChartEvent === 'undefined') {
4603 state.easyPieChartEvent = {
4610 var value = state.data.result[state.data.result.length - 1 - slot];
4611 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4612 var pcent = NETDATA.percentFromValueMax(value, max);
4614 state.easyPieChartEvent.value = value;
4615 state.easyPieChartEvent.pcent = pcent;
4616 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4618 if(state.easyPieChartEvent.timer === null) {
4619 state.easyPieChart_instance.disableAnimation();
4621 state.easyPieChartEvent.timer = setTimeout(function() {
4622 state.easyPieChartEvent.timer = null;
4623 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4624 }, NETDATA.options.current.charts_selection_animation_delay);
4630 NETDATA.easypiechartChartUpdate = function(state, data) {
4631 var value, max, pcent;
4633 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
4639 value = data.result[0];
4640 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4641 pcent = NETDATA.percentFromValueMax(value, max);
4644 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4645 state.easyPieChart_instance.update(pcent);
4649 NETDATA.easypiechartChartCreate = function(state, data) {
4650 var self = $(state.element);
4651 var chart = $(state.element_chart);
4653 var value = data.result[0];
4654 var max = self.data('easypiechart-max-value') || null;
4655 var adjust = self.data('easypiechart-adjust') || null;
4659 state.easyPieChartMax = null;
4662 state.easyPieChartMax = max;
4664 var pcent = NETDATA.percentFromValueMax(value, max);
4666 chart.data('data-percent', pcent);
4670 case 'width': size = state.chartHeight(); break;
4671 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4672 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4674 default: size = state.chartWidth(); break;
4676 state.element.style.width = size + 'px';
4677 state.element.style.height = size + 'px';
4679 var stroke = Math.floor(size / 22);
4680 if(stroke < 3) stroke = 2;
4682 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4683 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4684 state.easyPieChartLabel = document.createElement('span');
4685 state.easyPieChartLabel.className = 'easyPieChartLabel';
4686 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4687 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4688 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4689 state.element_chart.appendChild(state.easyPieChartLabel);
4691 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4692 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4693 state.easyPieChartTitle = document.createElement('span');
4694 state.easyPieChartTitle.className = 'easyPieChartTitle';
4695 state.easyPieChartTitle.innerHTML = state.title;
4696 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4697 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4698 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4699 state.element_chart.appendChild(state.easyPieChartTitle);
4701 var unitfontsize = Math.round(titlefontsize * 0.9);
4702 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4703 state.easyPieChartUnits = document.createElement('span');
4704 state.easyPieChartUnits.className = 'easyPieChartUnits';
4705 state.easyPieChartUnits.innerHTML = state.units;
4706 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4707 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4708 state.element_chart.appendChild(state.easyPieChartUnits);
4710 chart.easyPieChart({
4711 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4712 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4713 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4714 scaleLength: self.data('easypiechart-scalelength') || 5,
4715 lineCap: self.data('easypiechart-linecap') || 'round',
4716 lineWidth: self.data('easypiechart-linewidth') || stroke,
4717 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4718 size: self.data('easypiechart-size') || size,
4719 rotate: self.data('easypiechart-rotate') || 0,
4720 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4721 easing: self.data('easypiechart-easing') || undefined
4724 // when we just re-create the chart
4725 // do not animate the first update
4727 if(typeof state.easyPieChart_instance !== 'undefined')
4730 state.easyPieChart_instance = chart.data('easyPieChart');
4731 if(animate === false) state.easyPieChart_instance.disableAnimation();
4732 state.easyPieChart_instance.update(pcent);
4733 if(animate === false) state.easyPieChart_instance.enableAnimation();
4737 // ----------------------------------------------------------------------------------------------------------------
4740 NETDATA.gaugeInitialize = function(callback) {
4741 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4743 url: NETDATA.gauge_js,
4748 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4751 NETDATA.chartLibraries.gauge.enabled = false;
4752 NETDATA.error(100, NETDATA.gauge_js);
4754 .always(function() {
4755 if(typeof callback === "function")
4760 NETDATA.chartLibraries.gauge.enabled = false;
4761 if(typeof callback === "function")
4766 NETDATA.gaugeAnimation = function(state, status) {
4769 if(typeof status === 'boolean' && status === false)
4771 else if(typeof status === 'number')
4774 state.gauge_instance.animationSpeed = speed;
4775 state.___gaugeOld__.speed = speed;
4778 NETDATA.gaugeSet = function(state, value, min, max) {
4779 if(typeof value !== 'number') value = 0;
4780 if(typeof min !== 'number') min = 0;
4781 if(typeof max !== 'number') max = 0;
4782 if(value > max) max = value;
4783 if(value < min) min = value;
4792 // gauge.js has an issue if the needle
4793 // is smaller than min or larger than max
4794 // when we set the new values
4795 // the needle will go crazy
4797 // to prevent it, we always feed it
4798 // with a percentage, so that the needle
4799 // is always between min and max
4800 var pcent = (value - min) * 100 / (max - min);
4802 // these should never happen
4803 if(pcent < 0) pcent = 0;
4804 if(pcent > 100) pcent = 100;
4806 state.gauge_instance.set(pcent);
4808 state.___gaugeOld__.value = value;
4809 state.___gaugeOld__.min = min;
4810 state.___gaugeOld__.max = max;
4813 NETDATA.gaugeSetLabels = function(state, value, min, max) {
4814 if(state.___gaugeOld__.valueLabel !== value) {
4815 state.___gaugeOld__.valueLabel = value;
4816 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
4818 if(state.___gaugeOld__.minLabel !== min) {
4819 state.___gaugeOld__.minLabel = min;
4820 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
4822 if(state.___gaugeOld__.maxLabel !== max) {
4823 state.___gaugeOld__.maxLabel = max;
4824 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
4828 NETDATA.gaugeClearSelection = function(state) {
4829 if(typeof state.gaugeEvent !== 'undefined') {
4830 if(state.gaugeEvent.timer !== null)
4831 clearTimeout(state.gaugeEvent.timer);
4833 state.gaugeEvent.timer = null;
4836 if(state.isAutoRefreshed() === true && state.data !== null) {
4837 NETDATA.gaugeChartUpdate(state, state.data);
4840 NETDATA.gaugeAnimation(state, false);
4841 NETDATA.gaugeSet(state, null, null, null);
4842 NETDATA.gaugeSetLabels(state, null, null, null);
4845 NETDATA.gaugeAnimation(state, true);
4849 NETDATA.gaugeSetSelection = function(state, t) {
4850 if(state.timeIsVisible(t) !== true)
4851 return NETDATA.gaugeClearSelection(state);
4853 var slot = state.calculateRowForTime(t);
4854 if(slot < 0 || slot >= state.data.result.length)
4855 return NETDATA.gaugeClearSelection(state);
4857 if(typeof state.gaugeEvent === 'undefined') {
4858 state.gaugeEvent = {
4866 var value = state.data.result[state.data.result.length - 1 - slot];
4867 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
4870 state.gaugeEvent.value = value;
4871 state.gaugeEvent.max = max;
4872 state.gaugeEvent.min = min;
4873 NETDATA.gaugeSetLabels(state, value, min, max);
4875 if(state.gaugeEvent.timer === null) {
4876 NETDATA.gaugeAnimation(state, false);
4878 state.gaugeEvent.timer = setTimeout(function() {
4879 state.gaugeEvent.timer = null;
4880 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
4881 }, NETDATA.options.current.charts_selection_animation_delay);
4887 NETDATA.gaugeChartUpdate = function(state, data) {
4888 var value, min, max;
4890 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
4894 NETDATA.gaugeSetLabels(state, null, null, null);
4897 value = data.result[0];
4899 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
4900 if(value > max) max = value;
4901 NETDATA.gaugeSetLabels(state, value, min, max);
4904 NETDATA.gaugeSet(state, value, min, max);
4908 NETDATA.gaugeChartCreate = function(state, data) {
4909 var self = $(state.element);
4910 var chart = $(state.element_chart);
4912 var value = data.result[0];
4913 var max = self.data('gauge-max-value') || null;
4914 var adjust = self.data('gauge-adjust') || null;
4915 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
4916 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
4917 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
4918 var stopColor = self.data('gauge-stop-color') || void 0;
4919 var generateGradient = self.data('gauge-generate-gradient') || false;
4923 state.gaugeMax = null;
4926 state.gaugeMax = max;
4928 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
4930 // case 'width': width = height * ratio; break;
4932 // default: height = width / ratio; break;
4934 //state.element.style.width = width.toString() + 'px';
4935 //state.element.style.height = height.toString() + 'px';
4940 lines: 12, // The number of lines to draw
4941 angle: 0.15, // The length of each line
4942 lineWidth: 0.44, // 0.44 The line thickness
4944 length: 0.8, // 0.9 The radius of the inner circle
4945 strokeWidth: 0.035, // The rotation offset
4946 color: pointerColor // Fill color
4948 colorStart: startColor, // Colors
4949 colorStop: stopColor, // just experiment with them
4950 strokeColor: strokeColor, // to see which ones work best for you
4952 generateGradient: generateGradient,
4956 if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
4957 options.percentColors = [
4958 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
4959 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
4960 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
4961 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
4962 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
4963 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
4964 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
4965 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
4966 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
4967 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
4968 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
4971 state.gauge_canvas = document.createElement('canvas');
4972 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
4973 state.gauge_canvas.className = 'gaugeChart';
4974 state.gauge_canvas.width = width;
4975 state.gauge_canvas.height = height;
4976 state.element_chart.appendChild(state.gauge_canvas);
4978 var valuefontsize = Math.floor(height / 6);
4979 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
4980 state.gaugeChartLabel = document.createElement('span');
4981 state.gaugeChartLabel.className = 'gaugeChartLabel';
4982 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
4983 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
4984 state.element_chart.appendChild(state.gaugeChartLabel);
4986 var titlefontsize = Math.round(valuefontsize / 2);
4988 state.gaugeChartTitle = document.createElement('span');
4989 state.gaugeChartTitle.className = 'gaugeChartTitle';
4990 state.gaugeChartTitle.innerHTML = state.title;
4991 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
4992 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
4993 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
4994 state.element_chart.appendChild(state.gaugeChartTitle);
4996 var unitfontsize = Math.round(titlefontsize * 0.9);
4997 state.gaugeChartUnits = document.createElement('span');
4998 state.gaugeChartUnits.className = 'gaugeChartUnits';
4999 state.gaugeChartUnits.innerHTML = state.units;
5000 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5001 state.element_chart.appendChild(state.gaugeChartUnits);
5003 state.gaugeChartMin = document.createElement('span');
5004 state.gaugeChartMin.className = 'gaugeChartMin';
5005 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5006 state.element_chart.appendChild(state.gaugeChartMin);
5008 state.gaugeChartMax = document.createElement('span');
5009 state.gaugeChartMax.className = 'gaugeChartMax';
5010 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5011 state.element_chart.appendChild(state.gaugeChartMax);
5013 // when we just re-create the chart
5014 // do not animate the first update
5016 if(typeof state.gauge_instance !== 'undefined')
5019 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5021 state.___gaugeOld__ = {
5030 // we will always feed a percentage
5031 state.gauge_instance.minValue = 0;
5032 state.gauge_instance.maxValue = 100;
5034 NETDATA.gaugeAnimation(state, animate);
5035 NETDATA.gaugeSet(state, value, 0, max);
5036 NETDATA.gaugeSetLabels(state, value, 0, max);
5037 NETDATA.gaugeAnimation(state, true);
5041 // ----------------------------------------------------------------------------------------------------------------
5042 // Charts Libraries Registration
5044 NETDATA.chartLibraries = {
5046 initialize: NETDATA.dygraphInitialize,
5047 create: NETDATA.dygraphChartCreate,
5048 update: NETDATA.dygraphChartUpdate,
5049 resize: function(state) {
5050 if(typeof state.dygraph_instance.resize === 'function')
5051 state.dygraph_instance.resize();
5053 setSelection: NETDATA.dygraphSetSelection,
5054 clearSelection: NETDATA.dygraphClearSelection,
5055 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5058 format: function(state) { return 'json'; },
5059 options: function(state) { return 'ms|flip'; },
5060 legend: function(state) {
5061 if(this.isSparkline(state) === false)
5062 return 'right-side';
5066 autoresize: function(state) { return true; },
5067 max_updates_to_recreate: function(state) { return 5000; },
5068 track_colors: function(state) { return true; },
5069 pixels_per_point: function(state) {
5070 if(this.isSparkline(state) === false)
5076 isSparkline: function(state) {
5077 if(typeof state.dygraph_sparkline === 'undefined') {
5078 var t = $(state.element).data('dygraph-theme');
5079 if(t === 'sparkline')
5080 state.dygraph_sparkline = true;
5082 state.dygraph_sparkline = false;
5084 return state.dygraph_sparkline;
5088 initialize: NETDATA.sparklineInitialize,
5089 create: NETDATA.sparklineChartCreate,
5090 update: NETDATA.sparklineChartUpdate,
5092 setSelection: undefined, // function(state, t) { return true; },
5093 clearSelection: undefined, // function(state) { return true; },
5094 toolboxPanAndZoom: null,
5097 format: function(state) { return 'array'; },
5098 options: function(state) { return 'flip|abs'; },
5099 legend: function(state) { return null; },
5100 autoresize: function(state) { return false; },
5101 max_updates_to_recreate: function(state) { return 5000; },
5102 track_colors: function(state) { return false; },
5103 pixels_per_point: function(state) { return 3; }
5106 initialize: NETDATA.peityInitialize,
5107 create: NETDATA.peityChartCreate,
5108 update: NETDATA.peityChartUpdate,
5110 setSelection: undefined, // function(state, t) { return true; },
5111 clearSelection: undefined, // function(state) { return true; },
5112 toolboxPanAndZoom: null,
5115 format: function(state) { return 'ssvcomma'; },
5116 options: function(state) { return 'null2zero|flip|abs'; },
5117 legend: function(state) { return null; },
5118 autoresize: function(state) { return false; },
5119 max_updates_to_recreate: function(state) { return 5000; },
5120 track_colors: function(state) { return false; },
5121 pixels_per_point: function(state) { return 3; }
5124 initialize: NETDATA.morrisInitialize,
5125 create: NETDATA.morrisChartCreate,
5126 update: NETDATA.morrisChartUpdate,
5128 setSelection: undefined, // function(state, t) { return true; },
5129 clearSelection: undefined, // function(state) { return true; },
5130 toolboxPanAndZoom: null,
5133 format: function(state) { return 'json'; },
5134 options: function(state) { return 'objectrows|ms'; },
5135 legend: function(state) { return null; },
5136 autoresize: function(state) { return false; },
5137 max_updates_to_recreate: function(state) { return 50; },
5138 track_colors: function(state) { return false; },
5139 pixels_per_point: function(state) { return 15; }
5142 initialize: NETDATA.googleInitialize,
5143 create: NETDATA.googleChartCreate,
5144 update: NETDATA.googleChartUpdate,
5146 setSelection: undefined, //function(state, t) { return true; },
5147 clearSelection: undefined, //function(state) { return true; },
5148 toolboxPanAndZoom: null,
5151 format: function(state) { return 'datatable'; },
5152 options: function(state) { return ''; },
5153 legend: function(state) { return null; },
5154 autoresize: function(state) { return false; },
5155 max_updates_to_recreate: function(state) { return 300; },
5156 track_colors: function(state) { return false; },
5157 pixels_per_point: function(state) { return 4; }
5160 initialize: NETDATA.raphaelInitialize,
5161 create: NETDATA.raphaelChartCreate,
5162 update: NETDATA.raphaelChartUpdate,
5164 setSelection: undefined, // function(state, t) { return true; },
5165 clearSelection: undefined, // function(state) { return true; },
5166 toolboxPanAndZoom: null,
5169 format: function(state) { return 'json'; },
5170 options: function(state) { return ''; },
5171 legend: function(state) { return null; },
5172 autoresize: function(state) { return false; },
5173 max_updates_to_recreate: function(state) { return 5000; },
5174 track_colors: function(state) { return false; },
5175 pixels_per_point: function(state) { return 3; }
5178 initialize: NETDATA.c3Initialize,
5179 create: NETDATA.c3ChartCreate,
5180 update: NETDATA.c3ChartUpdate,
5182 setSelection: undefined, // function(state, t) { return true; },
5183 clearSelection: undefined, // function(state) { return true; },
5184 toolboxPanAndZoom: null,
5187 format: function(state) { return 'csvjsonarray'; },
5188 options: function(state) { return 'milliseconds'; },
5189 legend: function(state) { return null; },
5190 autoresize: function(state) { return false; },
5191 max_updates_to_recreate: function(state) { return 5000; },
5192 track_colors: function(state) { return false; },
5193 pixels_per_point: function(state) { return 15; }
5196 initialize: NETDATA.d3Initialize,
5197 create: NETDATA.d3ChartCreate,
5198 update: NETDATA.d3ChartUpdate,
5200 setSelection: undefined, // function(state, t) { return true; },
5201 clearSelection: undefined, // function(state) { return true; },
5202 toolboxPanAndZoom: null,
5205 format: function(state) { return 'json'; },
5206 options: function(state) { return ''; },
5207 legend: function(state) { return null; },
5208 autoresize: function(state) { return false; },
5209 max_updates_to_recreate: function(state) { return 5000; },
5210 track_colors: function(state) { return false; },
5211 pixels_per_point: function(state) { return 3; }
5214 initialize: NETDATA.easypiechartInitialize,
5215 create: NETDATA.easypiechartChartCreate,
5216 update: NETDATA.easypiechartChartUpdate,
5218 setSelection: NETDATA.easypiechartSetSelection,
5219 clearSelection: NETDATA.easypiechartClearSelection,
5220 toolboxPanAndZoom: null,
5223 format: function(state) { return 'array'; },
5224 options: function(state) { return 'absolute'; },
5225 legend: function(state) { return null; },
5226 autoresize: function(state) { return false; },
5227 max_updates_to_recreate: function(state) { return 5000; },
5228 track_colors: function(state) { return true; },
5229 pixels_per_point: function(state) { return 3; },
5233 initialize: NETDATA.gaugeInitialize,
5234 create: NETDATA.gaugeChartCreate,
5235 update: NETDATA.gaugeChartUpdate,
5237 setSelection: NETDATA.gaugeSetSelection,
5238 clearSelection: NETDATA.gaugeClearSelection,
5239 toolboxPanAndZoom: null,
5242 format: function(state) { return 'array'; },
5243 options: function(state) { return 'absolute'; },
5244 legend: function(state) { return null; },
5245 autoresize: function(state) { return false; },
5246 max_updates_to_recreate: function(state) { return 5000; },
5247 track_colors: function(state) { return true; },
5248 pixels_per_point: function(state) { return 3; },
5253 NETDATA.registerChartLibrary = function(library, url) {
5254 if(NETDATA.options.debug.libraries === true)
5255 console.log("registering chart library: " + library);
5257 NETDATA.chartLibraries[library].url = url;
5258 NETDATA.chartLibraries[library].initialized = true;
5259 NETDATA.chartLibraries[library].enabled = true;
5262 // ----------------------------------------------------------------------------------------------------------------
5265 NETDATA.requiredJs = [
5267 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5268 isAlreadyLoaded: function() {
5269 if(typeof $().emulateTransitionEnd == 'function')
5272 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5280 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5281 isAlreadyLoaded: function() { return false; }
5284 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5285 isAlreadyLoaded: function() { return false; }
5289 NETDATA.requiredCSS = [
5291 url: NETDATA.themes.current.bootstrap_css,
5292 isAlreadyLoaded: function() {
5293 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5300 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5301 isAlreadyLoaded: function() { return false; }
5304 url: NETDATA.themes.current.dashboard_css,
5305 isAlreadyLoaded: function() { return false; }
5308 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5309 isAlreadyLoaded: function() { return false; }
5313 NETDATA.loadRequiredJs = function(index, callback) {
5314 if(index >= NETDATA.requiredJs.length) {
5315 if(typeof callback === 'function')
5320 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5321 NETDATA.loadRequiredJs(++index, callback);
5325 if(NETDATA.options.debug.main_loop === true)
5326 console.log('loading ' + NETDATA.requiredJs[index].url);
5329 url: NETDATA.requiredJs[index].url,
5333 .success(function() {
5334 if(NETDATA.options.debug.main_loop === true)
5335 console.log('loaded ' + NETDATA.requiredJs[index].url);
5337 NETDATA.loadRequiredJs(++index, callback);
5340 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5344 NETDATA.loadRequiredCSS = function(index) {
5345 if(index >= NETDATA.requiredCSS.length)
5348 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5349 NETDATA.loadRequiredCSS(++index);
5353 if(NETDATA.options.debug.main_loop === true)
5354 console.log('loading ' + NETDATA.requiredCSS[index].url);
5356 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5357 NETDATA.loadRequiredCSS(++index);
5360 NETDATA.errorReset();
5361 NETDATA.loadRequiredCSS(0);
5363 NETDATA._loadjQuery(function() {
5364 NETDATA.loadRequiredJs(0, function() {
5365 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
5366 if(NETDATA.options.debug.main_loop === true)
5367 console.log('starting chart refresh thread');
5374 // window.NETDATA = NETDATA;
5375 // })(window, document);