1 // You can set the following variables before loading this script:
3 // var netdataNoDygraphs = true; // do not use dygraph
4 // var netdataNoSparklines = true; // do not use sparkline
5 // var netdataNoPeitys = true; // do not use peity
6 // var netdataNoGoogleCharts = true; // do not use google
7 // var netdataNoMorris = true; // do not use morris
8 // var netdataNoEasyPieChart = true; // do not use easy pie chart
9 // var netdataNoGauge = true; // do not use gauge.js
10 // var netdataNoD3 = true; // do not use D3
11 // var netdataNoC3 = true; // do not use C3
12 // var netdataNoBootstrap = true; // do not load bootstrap
13 // var netdataDontStart = true; // do not start the thread to process the charts
15 // You can also set the default netdata server, using the following.
16 // When this variable is not set, we assume the page is hosted on your
17 // netdata server already.
18 // var netdataServer = "http://yourhost:19999"; // set your NetData server
20 //(function(window, document, undefined) {
21 // fix IE issue with console
22 if(!window.console){ window.console = {log: function(){} }; }
25 var NETDATA = window.NETDATA || {};
27 // ----------------------------------------------------------------------------------------------------------------
28 // Detect the netdata server
30 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
31 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
32 NETDATA._scriptSource = function() {
35 if(typeof document.currentScript !== 'undefined') {
36 script = document.currentScript;
39 var all_scripts = document.getElementsByTagName('script');
40 script = all_scripts[all_scripts.length - 1];
43 if (typeof script.getAttribute.length !== 'undefined')
46 script = script.getAttribute('src', -1);
51 if(typeof netdataServer !== 'undefined')
52 NETDATA.serverDefault = netdataServer;
54 var s = NETDATA._scriptSource();
55 NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
58 if(NETDATA.serverDefault === null)
59 NETDATA.serverDefault = '';
60 else if(NETDATA.serverDefault.slice(-1) !== '/')
61 NETDATA.serverDefault += '/';
63 // default URLs for all the external files we need
64 // make them RELATIVE so that the whole thing can also be
65 // installed under a web server
66 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-1.12.0.min.js';
67 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity.min.js';
68 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline.min.js';
69 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart.min.js';
70 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge.min.js';
71 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined.js';
72 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter.js';
73 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-min.js';
74 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris.min.js';
75 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3.min.js';
76 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3.min.js';
77 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3.min.css';
78 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris.css';
79 NETDATA.google_js = 'https://www.google.com/jsapi';
83 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.min.css',
84 dashboard_css: NETDATA.serverDefault + 'dashboard.css',
85 background: '#FFFFFF',
86 foreground: '#000000',
89 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
90 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
91 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
92 '#329262', '#3B3EAC' ],
93 easypiechart_track: '#f0f0f0',
94 easypiechart_scale: '#dfe0e0',
95 gauge_pointer: '#C0C0C0',
96 gauge_stroke: '#F0F0F0',
100 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.slate.min.css',
101 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css',
102 background: '#272b30',
103 foreground: '#C8C8C8',
106 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
107 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
108 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
109 '#a6a479', '#a66da8' ],
111 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
112 '#3B3EAC', '#EE9911', '#BB44CC', '#C83E3E', '#990099', '#CC7700',
113 '#22AA99', '#109618', '#6633CC', '#DD4477', '#316395', '#8B0707',
114 '#329262', '#3B3EFF' ],
115 easypiechart_track: '#373b40',
116 easypiechart_scale: '#373b40',
117 gauge_pointer: '#474b50',
118 gauge_stroke: '#373b40',
119 gauge_gradient: false
123 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
124 NETDATA.themes.current = NETDATA.themes[netdataTheme];
126 NETDATA.themes.current = NETDATA.themes.default;
128 NETDATA.colors = NETDATA.themes.current.colors;
130 // these are the colors Google Charts are using
131 // we have them here to attempt emulate their look and feel on the other chart libraries
132 // http://there4.io/2012/05/02/google-chart-color-list/
133 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
134 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
135 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
137 // an alternative set
138 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
139 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
140 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
142 // ----------------------------------------------------------------------------------------------------------------
143 // the defaults for all charts
145 // if the user does not specify any of these, the following will be used
147 NETDATA.chartDefaults = {
148 host: NETDATA.serverDefault, // the server to get data from
149 width: '100%', // the chart width - can be null
150 height: '100%', // the chart height - can be null
151 min_width: null, // the chart minimum width - can be null
152 library: 'dygraph', // the graphing library to use
153 method: 'average', // the grouping method
154 before: 0, // panning
155 after: -600, // panning
156 pixels_per_point: 1, // the detail of the chart
157 fill_luminance: 0.8 // luminance of colors in solit areas
160 // ----------------------------------------------------------------------------------------------------------------
164 pauseCallback: null, // a callback when we are really paused
166 pause: false, // when enabled we don't auto-refresh the charts
168 targets: null, // an array of all the state objects that are
169 // currently active (independently of their
170 // viewport visibility)
172 updated_dom: true, // when true, the DOM has been updated with
173 // new elements we have to check.
175 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
176 // rendering charts continiously.
177 // used with .current.fast_render_timeframe
179 page_is_visible: true, // when true, this page is visible
181 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
182 // auto-refresher for some time (when a chart is
183 // performing pan or zoom, we need to stop refreshing
184 // all other charts, to have the maximum speed for
185 // rendering the chart that is panned or zoomed).
186 // Used with .current.global_pan_sync_time
188 last_resized: new Date().getTime(), // the timestamp of the last resize request
190 crossDomainAjax: false, // enable this to request crossDomain AJAX
192 last_page_scroll: 0, // the timestamp the last time the page was scrolled
194 // the current profile
195 // we may have many...
197 pixels_per_point: 1, // the minimum pixels per point for all charts
198 // increase this to speed javascript up
199 // each chart library has its own limit too
200 // the max of this and the chart library is used
201 // the final is calculated every time, so a change
202 // here will have immediate effect on the next chart
205 idle_between_charts: 100, // ms - how much time to wait between chart updates
207 fast_render_timeframe: 200, // ms - render continously until this time of continious
208 // rendering has been reached
209 // this setting is used to make it render e.g. 10
210 // charts at once, sleep idle_between_charts time
211 // and continue for another 10 charts.
213 idle_between_loops: 500, // ms - if all charts have been updated, wait this
214 // time before starting again.
216 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
218 idle_lost_focus: 500, // ms - when the window does not have focus, check
219 // if focus has been regained, every this time
221 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
222 // autorefreshing of charts is paused for this amount
225 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
226 // of time before setting up synchronized selections
229 sync_selection: true, // enable or disable selection sync
231 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
233 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
235 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
237 update_only_visible: true, // enable or disable visibility management
239 parallel_refresher: true, // enable parallel refresh of charts
241 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
243 destroy_on_hide: false, // destroy charts when they are not visible
245 show_help: true, // when enabled the charts will show some help
246 show_help_delay_show_ms: 500,
247 show_help_delay_hide_ms: 0,
249 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
251 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
252 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
254 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
256 smooth_plot: true, // enable smooth plot, where possible
258 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
260 color_fill_opacity_line: 1.0,
261 color_fill_opacity_area: 0.2,
262 color_fill_opacity_stacked: 0.8,
264 pan_and_zoom_step: 0.1, // the increment when panning and zooming with the toolbox
265 pan_and_zoom_step_multiplier_shift: 5,
266 pan_and_zoom_step_multiplier_alt: 10,
267 pan_and_zoom_step_multiplier_control: 2,
269 setOptionCallback: function() { ; }
277 chart_data_url: false,
278 chart_errors: false, // FIXME
287 // ----------------------------------------------------------------------------------------------------------------
288 // local storage options
290 NETDATA.localStorage = {
293 callback: {} // only used for resetting back to defaults
296 NETDATA.localStorageGet = function(key, def, callback) {
299 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
300 NETDATA.localStorage.default[key.toString()] = def;
301 NETDATA.localStorage.callback[key.toString()] = callback;
304 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
306 // console.log('localStorage: loading "' + key.toString() + '"');
307 ret = localStorage.getItem(key.toString());
308 if(ret === null || ret === 'undefined') {
309 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
310 localStorage.setItem(key.toString(), JSON.stringify(def));
314 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
315 ret = JSON.parse(ret);
316 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
320 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
325 if(typeof ret === 'undefined' || ret === 'undefined') {
326 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
330 NETDATA.localStorage.current[key.toString()] = ret;
334 NETDATA.localStorageSet = function(key, value, callback) {
335 if(typeof value === 'undefined' || value === 'undefined') {
336 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
339 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
340 NETDATA.localStorage.default[key.toString()] = value;
341 NETDATA.localStorage.current[key.toString()] = value;
342 NETDATA.localStorage.callback[key.toString()] = callback;
345 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
346 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
348 localStorage.setItem(key.toString(), JSON.stringify(value));
351 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
355 NETDATA.localStorage.current[key.toString()] = value;
359 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
361 if(typeof obj[i] === 'object') {
362 //console.log('object ' + prefix + '.' + i.toString());
363 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
367 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
371 NETDATA.setOption = function(key, value) {
372 if(key.toString() === 'setOptionCallback') {
373 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
374 NETDATA.options.current[key.toString()] = value;
375 NETDATA.options.current.setOptionCallback();
378 else if(NETDATA.options.current[key.toString()] !== value) {
379 var name = 'options.' + key.toString();
381 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
382 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
384 //console.log(NETDATA.localStorage);
385 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
386 //console.log(NETDATA.options);
387 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
389 if(typeof NETDATA.options.current.setOptionCallback === 'function')
390 NETDATA.options.current.setOptionCallback();
396 NETDATA.getOption = function(key) {
397 return NETDATA.options.current[key.toString()];
400 // read settings from local storage
401 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
403 // always start with this option enabled.
404 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
406 NETDATA.resetOptions = function() {
407 for(var i in NETDATA.localStorage.default) {
408 var a = i.split('.');
410 if(a[0] === 'options') {
411 if(a[1] === 'setOptionCallback') continue;
412 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
413 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
415 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
417 else if(a[0] === 'chart_heights') {
418 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
419 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
425 // ----------------------------------------------------------------------------------------------------------------
427 if(NETDATA.options.debug.main_loop === true)
428 console.log('welcome to NETDATA');
430 NETDATA.onresize = function() {
431 NETDATA.options.last_resized = new Date().getTime();
435 NETDATA.onscroll = function() {
436 // console.log('onscroll');
438 NETDATA.options.last_page_scroll = new Date().getTime();
439 if(NETDATA.options.targets === null) return;
441 // when the user scrolls he sees that we have
442 // hidden all the not-visible charts
443 // using this little function we try to switch
444 // the charts back to visible quickly
445 var targets = NETDATA.options.targets;
446 var len = targets.length;
447 while(len--) targets[len].isVisible();
450 window.onresize = NETDATA.onresize;
451 window.onscroll = NETDATA.onscroll;
453 // ----------------------------------------------------------------------------------------------------------------
456 NETDATA.errorCodes = {
457 100: { message: "Cannot load chart library", alert: true },
458 101: { message: "Cannot load jQuery", alert: true },
459 402: { message: "Chart library not found", alert: false },
460 403: { message: "Chart library not enabled/is failed", alert: false },
461 404: { message: "Chart not found", alert: false }
463 NETDATA.errorLast = {
469 NETDATA.error = function(code, msg) {
470 NETDATA.errorLast.code = code;
471 NETDATA.errorLast.message = msg;
472 NETDATA.errorLast.datetime = new Date().getTime();
474 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
476 if(NETDATA.errorCodes[code].alert)
477 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
480 NETDATA.errorReset = function() {
481 NETDATA.errorLast.code = 0;
482 NETDATA.errorLast.message = "You are doing fine!";
483 NETDATA.errorLast.datetime = 0;
486 // ----------------------------------------------------------------------------------------------------------------
489 // When multiple charts need the same chart, we avoid downloading it
490 // multiple times (and having it in browser memory multiple time)
491 // by using this registry.
493 // Every time we download a chart definition, we save it here with .add()
494 // Then we try to get it back with .get(). If that fails, we download it.
496 NETDATA.chartRegistry = {
499 fixid: function(id) {
500 return id.replace(/:/g, "_").replace(/\//g, "_");
503 add: function(host, id, data) {
504 host = this.fixid(host);
507 if(typeof this.charts[host] === 'undefined')
508 this.charts[host] = {};
510 //console.log('added ' + host + '/' + id);
511 this.charts[host][id] = data;
514 get: function(host, id) {
515 host = this.fixid(host);
518 if(typeof this.charts[host] === 'undefined')
521 if(typeof this.charts[host][id] === 'undefined')
524 //console.log('cached ' + host + '/' + id);
525 return this.charts[host][id];
528 downloadAll: function(host, callback) {
529 while(host.slice(-1) === '/')
530 host = host.substring(0, host.length - 1);
535 url: host + '/api/v1/charts',
536 crossDomain: NETDATA.options.crossDomainAjax,
540 .done(function(data) {
541 var h = NETDATA.chartRegistry.fixid(host);
542 //console.log('downloaded all charts from ' + host + ' (' + h + ')');
543 self.charts[h] = data.charts;
544 if(typeof callback === 'function')
548 if(typeof callback === 'function')
554 // ----------------------------------------------------------------------------------------------------------------
555 // Global Pan and Zoom on charts
557 // Using this structure are synchronize all the charts, so that
558 // when you pan or zoom one, all others are automatically refreshed
559 // to the same timespan.
561 NETDATA.globalPanAndZoom = {
562 seq: 0, // timestamp ms
563 // every time a chart is panned or zoomed
564 // we set the timestamp here
565 // then we use it as a sequence number
566 // to find if other charts are syncronized
569 master: null, // the master chart (state), to which all others
572 force_before_ms: null, // the timespan to sync all other charts
573 force_after_ms: null,
576 setMaster: function(state, after, before) {
577 if(NETDATA.options.current.sync_pan_and_zoom === false)
580 if(this.master !== null && this.master !== state)
581 this.master.resetChart(true, true);
583 var now = new Date().getTime();
586 this.force_after_ms = after;
587 this.force_before_ms = before;
588 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
592 clearMaster: function() {
593 if(this.master !== null) {
594 var st = this.master;
601 this.force_after_ms = null;
602 this.force_before_ms = null;
603 NETDATA.options.auto_refresher_stop_until = 0;
606 // is the given state the master of the global
607 // pan and zoom sync?
608 isMaster: function(state) {
609 if(this.master === state) return true;
613 // are we currently have a global pan and zoom sync?
614 isActive: function() {
615 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
619 // check if a chart, other than the master
620 // needs to be refreshed, due to the global pan and zoom
621 shouldBeAutoRefreshed: function(state) {
622 if(this.master === null || this.seq === 0)
625 //if(state.needsRecreation())
628 if(state.tm.pan_and_zoom_seq === this.seq)
635 // ----------------------------------------------------------------------------------------------------------------
636 // dimensions selection
639 // move color assignment to dimensions, here
641 dimensionStatus = function(parent, label, name_div, value_div, color) {
642 this.enabled = false;
643 this.parent = parent;
645 this.name_div = null;
646 this.value_div = null;
647 this.color = NETDATA.themes.current.foreground;
649 if(parent.selected === parent.unselected)
650 this.selected = true;
652 this.selected = false;
654 this.setOptions(name_div, value_div, color);
657 dimensionStatus.prototype.invalidate = function() {
658 this.name_div = null;
659 this.value_div = null;
660 this.enabled = false;
663 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
666 if(this.name_div != name_div) {
667 this.name_div = name_div;
668 this.name_div.title = this.label;
669 this.name_div.style.color = this.color;
670 if(this.selected === false)
671 this.name_div.className = 'netdata-legend-name not-selected';
673 this.name_div.className = 'netdata-legend-name selected';
676 if(this.value_div != value_div) {
677 this.value_div = value_div;
678 this.value_div.title = this.label;
679 this.value_div.style.color = this.color;
680 if(this.selected === false)
681 this.value_div.className = 'netdata-legend-value not-selected';
683 this.value_div.className = 'netdata-legend-value selected';
690 dimensionStatus.prototype.setHandler = function() {
691 if(this.enabled === false) return;
695 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
696 this.name_div.onclick = this.value_div.onclick = function(e) {
698 if(ds.isSelected()) {
700 if(e.shiftKey === true || e.ctrlKey === true) {
701 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
704 if(ds.parent.countSelected() === 0)
705 ds.parent.selectAll();
708 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
709 if(ds.parent.countSelected() === 1) {
710 ds.parent.selectAll();
713 ds.parent.selectNone();
719 // this is not selected
720 if(e.shiftKey === true || e.ctrlKey === true) {
721 // control or shift key is pressed -> select this too
725 // no key is pressed -> select only this
726 ds.parent.selectNone();
731 ds.parent.state.redrawChart();
735 dimensionStatus.prototype.select = function() {
736 if(this.enabled === false) return;
738 this.name_div.className = 'netdata-legend-name selected';
739 this.value_div.className = 'netdata-legend-value selected';
740 this.selected = true;
743 dimensionStatus.prototype.unselect = function() {
744 if(this.enabled === false) return;
746 this.name_div.className = 'netdata-legend-name not-selected';
747 this.value_div.className = 'netdata-legend-value hidden';
748 this.selected = false;
751 dimensionStatus.prototype.isSelected = function() {
752 return(this.enabled === true && this.selected === true);
755 // ----------------------------------------------------------------------------------------------------------------
757 dimensionsVisibility = function(state) {
760 this.dimensions = {};
761 this.selected_count = 0;
762 this.unselected_count = 0;
765 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
766 if(typeof this.dimensions[label] === 'undefined') {
768 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
771 this.dimensions[label].setOptions(name_div, value_div, color);
773 return this.dimensions[label];
776 dimensionsVisibility.prototype.dimensionGet = function(label) {
777 return this.dimensions[label];
780 dimensionsVisibility.prototype.invalidateAll = function() {
781 for(var d in this.dimensions)
782 this.dimensions[d].invalidate();
785 dimensionsVisibility.prototype.selectAll = function() {
786 for(var d in this.dimensions)
787 this.dimensions[d].select();
790 dimensionsVisibility.prototype.countSelected = function() {
792 for(var d in this.dimensions)
793 if(this.dimensions[d].isSelected()) i++;
798 dimensionsVisibility.prototype.selectNone = function() {
799 for(var d in this.dimensions)
800 this.dimensions[d].unselect();
803 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
804 var ret = new Array();
805 this.selected_count = 0;
806 this.unselected_count = 0;
808 for(var i = 0, len = array.length; i < len ; i++) {
809 var ds = this.dimensions[array[i]];
810 if(typeof ds === 'undefined') {
811 // console.log(array[i] + ' is not found');
816 if(ds.isSelected()) {
818 this.selected_count++;
822 this.unselected_count++;
826 if(this.selected_count === 0 && this.unselected_count !== 0) {
828 return this.selected2BooleanArray(array);
835 // ----------------------------------------------------------------------------------------------------------------
836 // global selection sync
838 NETDATA.globalSelectionSync = {
845 if(this.state !== null)
846 this.state.globalSelectionSyncStop();
850 if(this.state !== null) {
851 this.state.globalSelectionSyncDelay();
856 // ----------------------------------------------------------------------------------------------------------------
857 // Our state object, where all per-chart values are stored
859 chartState = function(element) {
860 var self = $(element);
861 this.element = element;
864 // all private functions should use 'that', instead of 'this'
868 * show an error instead of the chart
870 var error = function(msg) {
871 that.element.innerHTML = that.id + ': ' + msg;
872 that.enabled = false;
873 that.current = that.pan;
876 // GUID - a unique identifier for the chart
877 this.uuid = NETDATA.guid();
879 // string - the name of chart
880 this.id = self.data('netdata');
882 // string - the key for localStorage settings
883 this.settings_id = self.data('id') || null;
885 // the user given dimensions of the element
886 this.width = self.data('width') || NETDATA.chartDefaults.width;
887 this.height = self.data('height') || NETDATA.chartDefaults.height;
889 if(this.settings_id !== null) {
890 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
891 // this is the callback that will be called
892 // if and when the user resets all localStorage variables
895 resizeChartToHeight(height);
899 // string - the netdata server URL, without any path
900 this.host = self.data('host') || NETDATA.chartDefaults.host;
902 // make sure the host does not end with /
903 // all netdata API requests use absolute paths
904 while(this.host.slice(-1) === '/')
905 this.host = this.host.substring(0, this.host.length - 1);
907 // string - the grouping method requested by the user
908 this.method = self.data('method') || NETDATA.chartDefaults.method;
910 // the time-range requested by the user
911 this.after = self.data('after') || NETDATA.chartDefaults.after;
912 this.before = self.data('before') || NETDATA.chartDefaults.before;
914 // the pixels per point requested by the user
915 this.pixels_per_point = self.data('pixels-per-point') || 1;
916 this.points = self.data('points') || null;
918 // the dimensions requested by the user
919 this.dimensions = self.data('dimensions') || null;
921 // the chart library requested by the user
922 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
924 // object - the chart library used
929 this.colors_assigned = {};
930 this.colors_available = null;
932 // the element already created by the user
933 this.element_message = null;
935 // the element with the chart
936 this.element_chart = null;
938 // the element with the legend of the chart (if created by us)
939 this.element_legend = null;
940 this.element_legend_childs = {
950 this.chart_url = null; // string - the url to download chart info
951 this.chart = null; // object - the chart as downloaded from the server
953 this.title = self.data('title') || null; // the title of the chart
954 this.units = self.data('units') || null; // the units of the chart dimensions
955 this.append_options = self.data('append-options') || null; // the units of the chart dimensions
957 this.validated = false; // boolean - has the chart been validated?
958 this.enabled = true; // boolean - is the chart enabled for refresh?
959 this.paused = false; // boolean - is the chart paused for any reason?
960 this.selected = false; // boolean - is the chart shown a selection?
961 this.debug = false; // boolean - console.log() debug info about this chart
963 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
964 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
965 this.requested_after = null; // milliseconds - the timestamp of the request after param
966 this.requested_before = null; // milliseconds - the timestamp of the request before param
967 this.requested_padding = null;
969 this.view_before = 0;
974 force_update_at: 0, // the timestamp to force the update at
975 force_before_ms: null,
981 force_update_at: 0, // the timestamp to force the update at
982 force_before_ms: null,
988 force_update_at: 0, // the timestamp to force the update at
989 force_before_ms: null,
993 // this is a pointer to one of the sub-classes below
995 this.current = this.auto;
997 // check the requested library is available
998 // we don't initialize it here - it will be initialized when
999 // this chart will be first used
1000 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1001 NETDATA.error(402, that.library_name);
1002 error('chart library "' + that.library_name + '" is not found');
1005 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1006 NETDATA.error(403, that.library_name);
1007 error('chart library "' + that.library_name + '" is not enabled');
1011 that.library = NETDATA.chartLibraries[that.library_name];
1013 // milliseconds - the time the last refresh took
1014 this.refresh_dt_ms = 0;
1016 // if we need to report the rendering speed
1017 // find the element that needs to be updated
1018 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1020 if(refresh_dt_element_name !== null)
1021 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1023 this.refresh_dt_element = null;
1025 this.dimensions_visibility = new dimensionsVisibility(this);
1027 this._updating = false;
1029 // ============================================================================================================
1030 // PRIVATE FUNCTIONS
1032 var createDOM = function() {
1033 if(that.enabled === false) return;
1035 if(that.element_message !== null) that.element_message.innerHTML = '';
1036 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1037 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1039 that.element.innerHTML = '';
1041 that.element_message = document.createElement('div');
1042 that.element_message.className = ' netdata-message hidden';
1043 that.element.appendChild(that.element_message);
1045 that.element_chart = document.createElement('div');
1046 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1047 that.element.appendChild(that.element_chart);
1049 if(that.hasLegend() === true) {
1050 that.element.className = "netdata-container-with-legend";
1051 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1053 that.element_legend = document.createElement('div');
1054 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1055 that.element.appendChild(that.element_legend);
1058 that.element.className = "netdata-container";
1059 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1061 that.element_legend = null;
1063 that.element_legend_childs.series = null;
1065 if(typeof(that.width) === 'string')
1066 $(that.element).css('width', that.width);
1067 else if(typeof(that.width) === 'number')
1068 $(that.element).css('width', that.width + 'px');
1070 if(typeof(that.library.aspect_ratio) === 'undefined') {
1071 if(typeof(that.height) === 'string')
1072 $(that.element).css('height', that.height);
1073 else if(typeof(that.height) === 'number')
1074 $(that.element).css('height', that.height + 'px');
1077 var w = that.element.offsetWidth;
1078 if(w === null || w === 0) {
1079 // the div is hidden
1080 // this is resize the chart when next viewed
1081 that.tm.last_resized = 0;
1084 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1087 if(NETDATA.chartDefaults.min_width !== null)
1088 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1090 that.tm.last_dom_created = new Date().getTime();
1096 * initialize state viariables
1097 * destroy all (possibly) created state elements
1098 * create the basic DOM for a chart
1100 var init = function() {
1101 if(that.enabled === false) return;
1103 that.paused = false;
1104 that.selected = false;
1106 that.chart_created = false; // boolean - is the library.create() been called?
1107 that.updates_counter = 0; // numeric - the number of refreshes made so far
1108 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1109 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1112 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1113 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1114 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1116 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1117 last_updated: 0, // the timestamp the chart last updated with data
1118 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1120 // Used with NETDATA.globalPanAndZoom.seq
1121 last_visible_check: 0, // the time we last checked if it is visible
1122 last_resized: 0, // the time the chart was resized
1123 last_hidden: 0, // the time the chart was hidden
1124 last_unhidden: 0, // the time the chart was unhidden
1125 last_autorefreshed: 0 // the time the chart was last refreshed
1128 that.data = null; // the last data as downloaded from the netdata server
1129 that.data_url = 'invalid://'; // string - the last url used to update the chart
1130 that.data_points = 0; // number - the number of points returned from netdata
1131 that.data_after = 0; // milliseconds - the first timestamp of the data
1132 that.data_before = 0; // milliseconds - the last timestamp of the data
1133 that.data_update_every = 0; // milliseconds - the frequency to update the data
1135 that.tm.last_initialized = new Date().getTime();
1138 that.setMode('auto');
1141 var maxMessageFontSize = function() {
1142 // normally we want a font size, as tall as the element
1143 var h = that.element_message.clientHeight;
1145 // but give it some air, 20% let's say, or 5 pixels min
1146 var lost = Math.max(h * 0.2, 5);
1149 // center the text, verically
1150 var paddingTop = (lost - 5) / 2;
1152 // but check the width too
1153 // it should fit 10 characters in it
1154 var w = that.element_message.clientWidth / 10;
1156 paddingTop += (h - w) / 2;
1160 // and don't make it too huge
1161 // 5% of the screen size is good
1162 if(h > screen.height / 20) {
1163 paddingTop += (h - (screen.height / 20)) / 2;
1164 h = screen.height / 20;
1168 that.element_message.style.fontSize = h.toString() + 'px';
1169 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1172 var showMessage = function(msg) {
1173 that.element_message.className = 'netdata-message';
1174 that.element_message.innerHTML = msg;
1175 this.element_message.style.fontSize = 'x-small';
1176 that.element_message.style.paddingTop = '0px';
1177 that.___messageHidden___ = undefined;
1180 var showMessageIcon = function(icon) {
1181 that.element_message.innerHTML = icon;
1182 that.element_message.className = 'netdata-message icon';
1183 maxMessageFontSize();
1184 that.___messageHidden___ = undefined;
1187 var hideMessage = function() {
1188 if(typeof that.___messageHidden___ === 'undefined') {
1189 that.___messageHidden___ = true;
1190 that.element_message.className = 'netdata-message hidden';
1194 var showRendering = function() {
1196 if(that.chart !== null) {
1197 if(that.chart.chart_type === 'line')
1198 icon = '<i class="fa fa-line-chart"></i>';
1200 icon = '<i class="fa fa-area-chart"></i>';
1203 icon = '<i class="fa fa-area-chart"></i>';
1205 showMessageIcon(icon + ' netdata');
1208 var showLoading = function() {
1209 if(that.chart_created === false) {
1210 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1216 var isHidden = function() {
1217 if(typeof that.___chartIsHidden___ !== 'undefined')
1223 // hide the chart, when it is not visible - called from isVisible()
1224 var hideChart = function() {
1225 // hide it, if it is not already hidden
1226 if(isHidden() === true) return;
1228 if(that.chart_created === true) {
1229 // we should destroy it
1230 if(NETDATA.options.current.destroy_on_hide === true) {
1235 that.element_chart.style.display = 'none';
1236 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1237 that.tm.last_hidden = new Date().getTime();
1241 that.___chartIsHidden___ = true;
1244 // unhide the chart, when it is visible - called from isVisible()
1245 var unhideChart = function() {
1246 if(isHidden() === false) return;
1248 that.___chartIsHidden___ = undefined;
1249 that.updates_since_last_unhide = 0;
1251 if(that.chart_created === false) {
1252 // we need to re-initialize it, to show our background
1253 // logo in bootstrap tabs, until the chart loads
1257 that.tm.last_unhidden = new Date().getTime();
1258 that.element_chart.style.display = '';
1259 if(that.element_legend !== null) that.element_legend.style.display = '';
1265 var canBeRendered = function() {
1266 if(isHidden() === true || that.isVisible(true) === false)
1272 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1273 var callChartLibraryUpdateSafely = function(data) {
1276 if(canBeRendered() === false)
1279 if(NETDATA.options.debug.chart_errors === true)
1280 status = that.library.update(that, data);
1283 status = that.library.update(that, data);
1290 if(status === false) {
1291 error('chart failed to be updated as ' + that.library_name);
1298 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1299 var callChartLibraryCreateSafely = function(data) {
1302 if(canBeRendered() === false)
1305 if(NETDATA.options.debug.chart_errors === true)
1306 status = that.library.create(that, data);
1309 status = that.library.create(that, data);
1316 if(status === false) {
1317 error('chart failed to be created as ' + that.library_name);
1321 that.chart_created = true;
1322 that.updates_since_last_creation = 0;
1326 // ----------------------------------------------------------------------------------------------------------------
1329 // resizeChart() - private
1330 // to be called just before the chart library to make sure that
1331 // a properly sized dom is available
1332 var resizeChart = function() {
1333 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1334 if(that.chart_created === false) return;
1336 if(that.needsRecreation()) {
1339 else if(typeof that.library.resize === 'function') {
1340 that.library.resize(that);
1342 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1343 $(that.element_legend_childs.nano).nanoScroller();
1345 maxMessageFontSize();
1348 that.tm.last_resized = new Date().getTime();
1352 // this is the actual chart resize algorithm
1354 // - resize the entire container
1355 // - update the internal states
1356 // - resize the chart as the div changes height
1357 // - update the scrollbar of the legend
1358 var resizeChartToHeight = function(h) {
1360 that.element.style.height = h;
1362 if(that.settings_id !== null)
1363 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1365 var now = new Date().getTime();
1366 NETDATA.options.last_page_scroll = now;
1367 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1370 that.tm.last_resized = 0;
1374 this.resizeHandler = function(e) {
1377 if(typeof this.event_resize === 'undefined'
1378 || this.event_resize.chart_original_w === 'undefined'
1379 || this.event_resize.chart_original_h === 'undefined')
1380 this.event_resize = {
1381 chart_original_w: this.element.clientWidth,
1382 chart_original_h: this.element.clientHeight,
1386 if(e.type === 'touchstart') {
1387 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1388 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1391 this.event_resize.mouse_start_x = e.clientX;
1392 this.event_resize.mouse_start_y = e.clientY;
1395 this.event_resize.chart_start_w = this.element.clientWidth;
1396 this.event_resize.chart_start_h = this.element.clientHeight;
1397 this.event_resize.chart_last_w = this.element.clientWidth;
1398 this.event_resize.chart_last_h = this.element.clientHeight;
1400 var now = new Date().getTime();
1401 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1402 // double click / double tap event
1404 // the optimal height of the chart
1405 // showing the entire legend
1406 var optimal = this.event_resize.chart_last_h
1407 + this.element_legend_childs.content.scrollHeight
1408 - this.element_legend_childs.content.clientHeight;
1410 // if we are not optimal, be optimal
1411 if(this.event_resize.chart_last_h != optimal)
1412 resizeChartToHeight(optimal.toString() + 'px');
1414 // else if we do not have the original height
1415 // reset to the original height
1416 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1417 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1420 this.event_resize.last = now;
1422 // process movement event
1423 document.onmousemove =
1424 document.ontouchmove =
1425 this.element_legend_childs.resize_handler.onmousemove =
1426 this.element_legend_childs.resize_handler.ontouchmove =
1431 case 'mousemove': y = e.clientY; break;
1432 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1436 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1438 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1439 resizeChartToHeight(newH.toString() + 'px');
1440 that.event_resize.chart_last_h = newH;
1445 // process end event
1446 document.onmouseup =
1447 document.ontouchend =
1448 this.element_legend_childs.resize_handler.onmouseup =
1449 this.element_legend_childs.resize_handler.ontouchend =
1451 // remove all the hooks
1452 document.onmouseup =
1453 document.onmousemove =
1454 document.ontouchmove =
1455 document.ontouchend =
1456 that.element_legend_childs.resize_handler.onmousemove =
1457 that.element_legend_childs.resize_handler.ontouchmove =
1458 that.element_legend_childs.resize_handler.onmouseout =
1459 that.element_legend_childs.resize_handler.onmouseup =
1460 that.element_legend_childs.resize_handler.ontouchend =
1463 // allow auto-refreshes
1464 NETDATA.options.auto_refresher_stop_until = 0;
1470 var noDataToShow = function() {
1471 showMessageIcon('<i class="fa fa-warning"></i> empty');
1472 that.legendUpdateDOM();
1473 that.tm.last_autorefreshed = new Date().getTime();
1474 // that.data_update_every = 30 * 1000;
1475 //that.element_chart.style.display = 'none';
1476 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1477 //that.___chartIsHidden___ = true;
1480 // ============================================================================================================
1483 this.error = function(msg) {
1487 this.setMode = function(m) {
1488 if(this.current !== null && this.current.name === m) return;
1491 this.current = this.auto;
1492 else if(m === 'pan')
1493 this.current = this.pan;
1494 else if(m === 'zoom')
1495 this.current = this.zoom;
1497 this.current = this.auto;
1499 this.current.force_update_at = 0;
1500 this.current.force_before_ms = null;
1501 this.current.force_after_ms = null;
1503 this.tm.last_mode_switch = new Date().getTime();
1506 // ----------------------------------------------------------------------------------------------------------------
1507 // global selection sync
1509 // prevent to global selection sync for some time
1510 this.globalSelectionSyncDelay = function(ms) {
1511 if(NETDATA.options.current.sync_selection === false)
1514 if(typeof ms === 'number')
1515 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
1517 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
1520 // can we globally apply selection sync?
1521 this.globalSelectionSyncAbility = function() {
1522 if(NETDATA.options.current.sync_selection === false)
1525 if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
1531 this.globalSelectionSyncIsMaster = function() {
1532 if(NETDATA.globalSelectionSync.state === this)
1538 // this chart is the master of the global selection sync
1539 this.globalSelectionSyncBeMaster = function() {
1541 if(this.globalSelectionSyncIsMaster()) {
1542 if(this.debug === true)
1543 this.log('sync: I am the master already.');
1548 if(NETDATA.globalSelectionSync.state) {
1549 if(this.debug === true)
1550 this.log('sync: I am not the sync master. Resetting global sync.');
1552 this.globalSelectionSyncStop();
1555 // become the master
1556 if(this.debug === true)
1557 this.log('sync: becoming sync master.');
1559 this.selected = true;
1560 NETDATA.globalSelectionSync.state = this;
1562 // find the all slaves
1563 var targets = NETDATA.options.targets;
1564 var len = targets.length;
1569 if(this.debug === true)
1570 st.log('sync: not adding me to sync');
1572 else if(st.globalSelectionSyncIsEligible()) {
1573 if(this.debug === true)
1574 st.log('sync: adding to sync as slave');
1576 st.globalSelectionSyncBeSlave();
1580 // this.globalSelectionSyncDelay(100);
1583 // can the chart participate to the global selection sync as a slave?
1584 this.globalSelectionSyncIsEligible = function() {
1585 if(this.enabled === true
1586 && this.library !== null
1587 && typeof this.library.setSelection === 'function'
1588 && this.isVisible() === true
1589 && this.chart_created === true)
1595 // this chart becomes a slave of the global selection sync
1596 this.globalSelectionSyncBeSlave = function() {
1597 if(NETDATA.globalSelectionSync.state !== this)
1598 NETDATA.globalSelectionSync.slaves.push(this);
1601 // sync all the visible charts to the given time
1602 // this is to be called from the chart libraries
1603 this.globalSelectionSync = function(t) {
1604 if(this.globalSelectionSyncAbility() === false) {
1605 if(this.debug === true)
1606 this.log('sync: cannot sync (yet?).');
1611 if(this.globalSelectionSyncIsMaster() === false) {
1612 if(this.debug === true)
1613 this.log('sync: trying to be sync master.');
1615 this.globalSelectionSyncBeMaster();
1617 if(this.globalSelectionSyncAbility() === false) {
1618 if(this.debug === true)
1619 this.log('sync: cannot sync (yet?).');
1625 NETDATA.globalSelectionSync.last_t = t;
1626 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1631 // stop syncing all charts to the given time
1632 this.globalSelectionSyncStop = function() {
1633 if(NETDATA.globalSelectionSync.slaves.length) {
1634 if(this.debug === true)
1635 this.log('sync: cleaning up...');
1637 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1639 if(that.debug === true)
1640 st.log('sync: not adding me to sync stop');
1643 if(that.debug === true)
1644 st.log('sync: removed slave from sync');
1646 st.clearSelection();
1650 NETDATA.globalSelectionSync.last_t = 0;
1651 NETDATA.globalSelectionSync.slaves = [];
1652 NETDATA.globalSelectionSync.state = null;
1655 this.clearSelection();
1658 this.setSelection = function(t) {
1659 if(typeof this.library.setSelection === 'function') {
1660 if(this.library.setSelection(this, t) === true)
1661 this.selected = true;
1663 this.selected = false;
1665 else this.selected = true;
1667 if(this.selected === true && this.debug === true)
1668 this.log('selection set to ' + t.toString());
1670 return this.selected;
1673 this.clearSelection = function() {
1674 if(this.selected === true) {
1675 if(typeof this.library.clearSelection === 'function') {
1676 if(this.library.clearSelection(this) === true)
1677 this.selected = false;
1679 this.selected = true;
1681 else this.selected = false;
1683 if(this.selected === false && this.debug === true)
1684 this.log('selection cleared');
1689 return this.selected;
1692 // find if a timestamp (ms) is shown in the current chart
1693 this.timeIsVisible = function(t) {
1694 if(t >= this.data_after && t <= this.data_before)
1699 this.calculateRowForTime = function(t) {
1700 if(this.timeIsVisible(t) === false) return -1;
1701 return Math.floor((t - this.data_after) / this.data_update_every);
1704 // ----------------------------------------------------------------------------------------------------------------
1707 this.log = function(msg) {
1708 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
1711 this.pauseChart = function() {
1712 if(this.paused === false) {
1713 if(this.debug === true)
1714 this.log('pauseChart()');
1720 this.unpauseChart = function() {
1721 if(this.paused === true) {
1722 if(this.debug === true)
1723 this.log('unpauseChart()');
1725 this.paused = false;
1729 this.resetChart = function(dont_clear_master, dont_update) {
1730 if(this.debug === true)
1731 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
1733 if(typeof dont_clear_master === 'undefined')
1734 dont_clear_master = false;
1736 if(typeof dont_update === 'undefined')
1737 dont_update = false;
1739 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
1740 if(this.debug === true)
1741 this.log('resetChart() diverting to clearMaster().');
1742 // this will call us back with master === true
1743 NETDATA.globalPanAndZoom.clearMaster();
1747 this.clearSelection();
1749 this.tm.pan_and_zoom_seq = 0;
1751 this.setMode('auto');
1752 this.current.force_update_at = 0;
1753 this.current.force_before_ms = null;
1754 this.current.force_after_ms = null;
1755 this.tm.last_autorefreshed = 0;
1756 this.paused = false;
1757 this.selected = false;
1758 this.enabled = true;
1759 // this.debug = false;
1761 // do not update the chart here
1762 // or the chart will flip-flop when it is the master
1763 // of a selection sync and another chart becomes
1766 if(dont_update !== true && this.isVisible() === true) {
1771 this.updateChartPanOrZoom = function(after, before) {
1772 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
1775 if(this.debug === true)
1778 if(before < after) {
1779 this.log(logme + 'flipped parameters, rejecting it.');
1783 if(typeof this.fixed_min_duration === 'undefined')
1784 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1786 var min_duration = this.fixed_min_duration;
1787 var current_duration = Math.round(this.view_before - this.view_after);
1789 // round the numbers
1790 after = Math.round(after);
1791 before = Math.round(before);
1793 // align them to update_every
1794 // stretching them further away
1795 after -= after % this.data_update_every;
1796 before += this.data_update_every - (before % this.data_update_every);
1798 // the final wanted duration
1799 var wanted_duration = before - after;
1801 // to allow panning, accept just a point below our minimum
1802 if((current_duration - this.data_update_every) < min_duration)
1803 min_duration = current_duration - this.data_update_every;
1805 // we do it, but we adjust to minimum size and return false
1806 // when the wanted size is below the current and the minimum
1808 if(wanted_duration < current_duration && wanted_duration < min_duration) {
1809 if(this.debug === true)
1810 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1812 min_duration = this.fixed_min_duration;
1814 var dt = (min_duration - wanted_duration) / 2;
1817 wanted_duration = before - after;
1821 var tolerance = this.data_update_every * 2;
1822 var movement = Math.abs(before - this.view_before);
1824 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
1825 if(this.debug === true)
1826 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);
1830 if(this.current.name === 'auto') {
1831 this.log(logme + 'caller called me with mode: ' + this.current.name);
1832 this.setMode('pan');
1835 if(this.debug === true)
1836 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);
1838 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1839 this.current.force_after_ms = after;
1840 this.current.force_before_ms = before;
1841 NETDATA.globalPanAndZoom.setMaster(this, after, before);
1845 this.legendFormatValue = function(value) {
1846 if(value === null || value === 'undefined') return '-';
1847 if(typeof value !== 'number') return value;
1849 var abs = Math.abs(value);
1850 if(abs >= 1000) return (Math.round(value)).toLocaleString();
1851 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1852 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
1853 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1854 return (Math.round(value * 10000) / 10000).toLocaleString();
1857 this.legendSetLabelValue = function(label, value) {
1858 var series = this.element_legend_childs.series[label];
1859 if(typeof series === 'undefined') return;
1860 if(series.value === null && series.user === null) return;
1862 // if the value has not changed, skip DOM update
1863 //if(series.last === value) return;
1866 if(typeof value === 'number') {
1867 var v = Math.abs(value);
1868 s = r = this.legendFormatValue(value);
1870 if(typeof series.last === 'number') {
1871 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1872 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1873 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1875 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1880 series.last = value;
1883 if(series.value !== null) series.value.innerHTML = s;
1884 if(series.user !== null) series.user.innerHTML = r;
1887 this.legendSetDate = function(ms) {
1888 if(typeof ms !== 'number') {
1889 this.legendShowUndefined();
1893 var d = new Date(ms);
1895 if(this.element_legend_childs.title_date)
1896 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
1898 if(this.element_legend_childs.title_time)
1899 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
1901 if(this.element_legend_childs.title_units)
1902 this.element_legend_childs.title_units.innerHTML = this.units;
1905 this.legendShowUndefined = function() {
1906 if(this.element_legend_childs.title_date)
1907 this.element_legend_childs.title_date.innerHTML = ' ';
1909 if(this.element_legend_childs.title_time)
1910 this.element_legend_childs.title_time.innerHTML = this.chart.name;
1912 if(this.element_legend_childs.title_units)
1913 this.element_legend_childs.title_units.innerHTML = ' ';
1915 if(this.data && this.element_legend_childs.series !== null) {
1916 var labels = this.data.dimension_names;
1917 var i = labels.length;
1919 var label = labels[i];
1921 if(typeof label === 'undefined') continue;
1922 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1923 this.legendSetLabelValue(label, null);
1928 this.legendShowLatestValues = function() {
1929 if(this.chart === null) return;
1930 if(this.selected) return;
1932 if(this.data === null || this.element_legend_childs.series === null) {
1933 this.legendShowUndefined();
1937 var show_undefined = true;
1938 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
1939 show_undefined = false;
1941 if(show_undefined) {
1942 this.legendShowUndefined();
1946 this.legendSetDate(this.view_before);
1948 var labels = this.data.dimension_names;
1949 var i = labels.length;
1951 var label = labels[i];
1953 if(typeof label === 'undefined') continue;
1954 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1957 this.legendSetLabelValue(label, null);
1959 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
1963 this.legendReset = function() {
1964 this.legendShowLatestValues();
1967 // this should be called just ONCE per dimension per chart
1968 this._chartDimensionColor = function(label) {
1969 if(this.colors === null) this.chartColors();
1971 if(typeof this.colors_assigned[label] === 'undefined') {
1972 if(this.colors_available.length === 0) {
1973 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
1974 this.colors_available.push(NETDATA.themes.current.colors[i]);
1977 this.colors_assigned[label] = this.colors_available.shift();
1979 if(this.debug === true)
1980 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
1983 if(this.debug === true)
1984 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
1987 this.colors.push(this.colors_assigned[label]);
1988 return this.colors_assigned[label];
1991 this.chartColors = function() {
1992 if(this.colors !== null) return this.colors;
1994 this.colors = new Array();
1995 this.colors_available = new Array();
1997 var c = $(this.element).data('colors');
1998 // this.log('read colors: ' + c);
1999 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2000 if(typeof c !== 'string') {
2001 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2008 for(var i = 0, len = c.length; i < len ; i++) {
2010 this.colors_available.push(c[i]);
2011 // this.log('adding color: ' + c[i]);
2017 // push all the standard colors too
2018 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2019 this.colors_available.push(NETDATA.themes.current.colors[i]);
2024 this.legendUpdateDOM = function() {
2027 // check that the legend DOM is up to date for the downloaded dimensions
2028 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2029 // this.log('the legend does not have any series - requesting legend update');
2032 else if(this.data === null) {
2033 // this.log('the chart does not have any data - requesting legend update');
2036 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2040 var labels = this.data.dimension_names.toString();
2041 if(labels !== this.element_legend_childs.series.labels_key) {
2044 if(this.debug === true)
2045 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2049 if(needed === false) {
2050 // make sure colors available
2053 // do we have to update the current values?
2054 // we do this, only when the visible chart is current
2055 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2056 if(this.debug === true)
2057 this.log('chart is in latest position... updating values on legend...');
2059 //var labels = this.data.dimension_names;
2060 //var i = labels.length;
2062 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2066 if(this.colors === null) {
2067 // this is the first time we update the chart
2068 // let's assign colors to all dimensions
2069 if(this.library.track_colors() === true)
2070 for(var dim in this.chart.dimensions)
2071 this._chartDimensionColor(this.chart.dimensions[dim].name);
2073 // we will re-generate the colors for the chart
2074 // based on the selected dimensions
2077 if(this.debug === true)
2078 this.log('updating Legend DOM');
2080 // mark all dimensions as invalid
2081 this.dimensions_visibility.invalidateAll();
2083 var genLabel = function(state, parent, name, count) {
2084 var color = state._chartDimensionColor(name);
2086 var user_element = null;
2087 var user_id = self.data('show-value-of-' + name + '-at') || null;
2088 if(user_id !== null) {
2089 user_element = document.getElementById(user_id) || null;
2090 if(user_element === null)
2091 me.log('Cannot find element with id: ' + user_id);
2094 state.element_legend_childs.series[name] = {
2095 name: document.createElement('span'),
2096 value: document.createElement('span'),
2101 var label = state.element_legend_childs.series[name];
2103 // create the dimension visibility tracking for this label
2104 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2106 var rgb = NETDATA.colorHex2Rgb(color);
2107 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2108 + state.chart.chart_type
2109 + '" style="background-color: '
2110 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2111 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2113 var text = document.createTextNode(' ' + name);
2114 label.name.appendChild(text);
2117 parent.appendChild(document.createElement('br'));
2119 parent.appendChild(label.name);
2120 parent.appendChild(label.value);
2123 var content = document.createElement('div');
2125 if(this.hasLegend()) {
2126 this.element_legend_childs = {
2128 resize_handler: document.createElement('div'),
2129 toolbox: document.createElement('div'),
2130 toolbox_left: document.createElement('div'),
2131 toolbox_right: document.createElement('div'),
2132 toolbox_reset: document.createElement('div'),
2133 toolbox_zoomin: document.createElement('div'),
2134 toolbox_zoomout: document.createElement('div'),
2135 toolbox_volume: document.createElement('div'),
2136 title_date: document.createElement('span'),
2137 title_time: document.createElement('span'),
2138 title_units: document.createElement('span'),
2139 nano: document.createElement('div'),
2141 paneClass: 'netdata-legend-series-pane',
2142 sliderClass: 'netdata-legend-series-slider',
2143 contentClass: 'netdata-legend-series-content',
2144 enabledClass: '__enabled',
2145 flashedClass: '__flashed',
2146 activeClass: '__active',
2148 alwaysVisible: true,
2154 this.element_legend.innerHTML = '';
2156 if(this.library.toolboxPanAndZoom !== null) {
2158 function get_pan_and_zoom_step(event) {
2160 return NETDATA.options.current.pan_and_zoom_step * NETDATA.options.current.pan_and_zoom_step_multiplier_shift;
2161 else if (event.altKey)
2162 return NETDATA.options.current.pan_and_zoom_step * NETDATA.options.current.pan_and_zoom_step_multiplier_alt;
2163 else if (event.ctrlKey)
2164 return NETDATA.options.current.pan_and_zoom_step * NETDATA.options.current.pan_and_zoom_step_multiplier_control;
2166 return NETDATA.options.current.pan_and_zoom_step;
2169 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2170 this.element.appendChild(this.element_legend_childs.toolbox);
2172 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2173 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2174 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2175 this.element_legend_childs.toolbox_left.onclick = function(e) {
2178 var dt = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2179 var before = that.view_before - dt;
2180 var after = that.view_after - dt;
2181 if(after >= that.netdata_first)
2182 that.library.toolboxPanAndZoom(that, after, before);
2184 if(NETDATA.options.current.show_help === true)
2185 $(this.element_legend_childs.toolbox_left).popover({
2190 placement: 'bottom',
2191 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2193 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>'
2197 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2198 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2199 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2200 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2202 NETDATA.resetAllCharts(that);
2204 if(NETDATA.options.current.show_help === true)
2205 $(this.element_legend_childs.toolbox_reset).popover({
2210 placement: 'bottom',
2211 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2212 title: 'Chart Reset',
2213 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>'
2216 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2217 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2218 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2219 this.element_legend_childs.toolbox_right.onclick = function(e) {
2221 var dt = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2222 var before = that.view_before + dt;
2223 var after = that.view_after + dt;
2224 if(before <= that.netdata_last)
2225 that.library.toolboxPanAndZoom(that, after, before);
2227 if(NETDATA.options.current.show_help === true)
2228 $(this.element_legend_childs.toolbox_right).popover({
2233 placement: 'bottom',
2234 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2236 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>'
2240 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2241 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2242 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2243 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2245 var dt = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2246 var before = that.view_before - dt;
2247 var after = that.view_after + dt;
2248 that.library.toolboxPanAndZoom(that, after, before);
2250 if(NETDATA.options.current.show_help === true)
2251 $(this.element_legend_childs.toolbox_zoomin).popover({
2256 placement: 'bottom',
2257 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2258 title: 'Chart Zoom In',
2259 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>'
2262 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2263 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2264 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2265 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2267 var dt = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2268 var before = that.view_before + dt;
2269 var after = that.view_after - dt;
2271 that.library.toolboxPanAndZoom(that, after, before);
2273 if(NETDATA.options.current.show_help === true)
2274 $(this.element_legend_childs.toolbox_zoomout).popover({
2279 placement: 'bottom',
2280 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2281 title: 'Chart Zoom Out',
2282 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>'
2285 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2286 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2287 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2288 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2289 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2290 //e.preventDefault();
2291 //alert('clicked toolbox_volume on ' + that.id);
2295 this.element_legend_childs.toolbox = null;
2296 this.element_legend_childs.toolbox_left = null;
2297 this.element_legend_childs.toolbox_reset = null;
2298 this.element_legend_childs.toolbox_right = null;
2299 this.element_legend_childs.toolbox_zoomin = null;
2300 this.element_legend_childs.toolbox_zoomout = null;
2301 this.element_legend_childs.toolbox_volume = null;
2304 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2305 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2306 this.element.appendChild(this.element_legend_childs.resize_handler);
2307 if(NETDATA.options.current.show_help === true)
2308 $(this.element_legend_childs.resize_handler).popover({
2313 placement: 'bottom',
2314 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2315 title: 'Chart Resize',
2316 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>'
2320 this.element_legend_childs.resize_handler.onmousedown =
2322 that.resizeHandler(e);
2326 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2327 that.resizeHandler(e);
2330 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2331 this.element_legend.appendChild(this.element_legend_childs.title_date);
2333 this.element_legend.appendChild(document.createElement('br'));
2335 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2336 this.element_legend.appendChild(this.element_legend_childs.title_time);
2338 this.element_legend.appendChild(document.createElement('br'));
2340 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2341 this.element_legend.appendChild(this.element_legend_childs.title_units);
2343 this.element_legend.appendChild(document.createElement('br'));
2345 this.element_legend_childs.nano.className = 'netdata-legend-series';
2346 this.element_legend.appendChild(this.element_legend_childs.nano);
2348 content.className = 'netdata-legend-series-content';
2349 this.element_legend_childs.nano.appendChild(content);
2351 if(NETDATA.options.current.show_help === true)
2352 $(content).popover({
2357 placement: 'bottom',
2358 title: 'Chart Legend',
2359 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2360 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>'
2364 this.element_legend_childs = {
2366 resize_handler: null,
2369 toolbox_right: null,
2370 toolbox_reset: null,
2371 toolbox_zoomin: null,
2372 toolbox_zoomout: null,
2373 toolbox_volume: null,
2384 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2385 if(this.debug === true)
2386 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2388 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2389 genLabel(this, content, this.data.dimension_names[i], i);
2393 var tmp = new Array();
2394 for(var dim in this.chart.dimensions) {
2395 tmp.push(this.chart.dimensions[dim].name);
2396 genLabel(this, content, this.chart.dimensions[dim].name, i);
2398 this.element_legend_childs.series.labels_key = tmp.toString();
2399 if(this.debug === true)
2400 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2403 // create a hidden div to be used for hidding
2404 // the original legend of the chart library
2405 var el = document.createElement('div');
2406 if(this.element_legend !== null)
2407 this.element_legend.appendChild(el);
2408 el.style.display = 'none';
2410 this.element_legend_childs.hidden = document.createElement('div');
2411 el.appendChild(this.element_legend_childs.hidden);
2413 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2414 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2416 this.legendShowLatestValues();
2419 this.hasLegend = function() {
2420 if(typeof this.___hasLegendCache___ !== 'undefined')
2421 return this.___hasLegendCache___;
2424 if(this.library && this.library.legend(this) === 'right-side') {
2425 var legend = $(this.element).data('legend') || 'yes';
2426 if(legend === 'yes') leg = true;
2429 this.___hasLegendCache___ = leg;
2433 this.legendWidth = function() {
2434 return (this.hasLegend())?140:0;
2437 this.legendHeight = function() {
2438 return $(this.element).height();
2441 this.chartWidth = function() {
2442 return $(this.element).width() - this.legendWidth();
2445 this.chartHeight = function() {
2446 return $(this.element).height();
2449 this.chartPixelsPerPoint = function() {
2450 // force an options provided detail
2451 var px = this.pixels_per_point;
2453 if(this.library && px < this.library.pixels_per_point(this))
2454 px = this.library.pixels_per_point(this);
2456 if(px < NETDATA.options.current.pixels_per_point)
2457 px = NETDATA.options.current.pixels_per_point;
2462 this.needsRecreation = function() {
2464 this.chart_created === true
2466 && this.library.autoresize() === false
2467 && this.tm.last_resized < NETDATA.options.last_resized
2471 this.chartURL = function() {
2472 var after, before, points_multiplier = 1;
2473 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2474 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2476 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2477 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2478 this.view_after = after * 1000;
2479 this.view_before = before * 1000;
2481 this.requested_padding = null;
2482 points_multiplier = 1;
2484 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2485 this.tm.pan_and_zoom_seq = 0;
2487 before = Math.round(this.current.force_before_ms / 1000);
2488 after = Math.round(this.current.force_after_ms / 1000);
2489 this.view_after = after * 1000;
2490 this.view_before = before * 1000;
2492 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2493 this.requested_padding = Math.round((before - after) / 2);
2494 after -= this.requested_padding;
2495 before += this.requested_padding;
2496 this.requested_padding *= 1000;
2497 points_multiplier = 2;
2500 this.current.force_before_ms = null;
2501 this.current.force_after_ms = null;
2504 this.tm.pan_and_zoom_seq = 0;
2506 before = this.before;
2508 this.view_after = after * 1000;
2509 this.view_before = before * 1000;
2511 this.requested_padding = null;
2512 points_multiplier = 1;
2515 this.requested_after = after * 1000;
2516 this.requested_before = before * 1000;
2518 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2520 // build the data URL
2521 this.data_url = this.host + this.chart.data_url;
2522 this.data_url += "&format=" + this.library.format();
2523 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2524 this.data_url += "&group=" + this.method;
2525 this.data_url += "&options=" + this.library.options(this);
2526 this.data_url += '|jsonwrap';
2528 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2529 this.data_url += '|nonzero';
2531 if(this.append_options !== null)
2532 this.data_url += '|' + this.append_options.toString();
2535 this.data_url += "&after=" + after.toString();
2538 this.data_url += "&before=" + before.toString();
2541 this.data_url += "&dimensions=" + this.dimensions;
2543 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2544 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2547 this.redrawChart = function() {
2548 if(this.data !== null)
2549 this.updateChartWithData(this.data);
2552 this.updateChartWithData = function(data) {
2553 if(this.debug === true)
2554 this.log('updateChartWithData() called.');
2556 this._updating = false;
2558 // this may force the chart to be re-created
2562 this.updates_counter++;
2563 this.updates_since_last_unhide++;
2564 this.updates_since_last_creation++;
2566 var started = new Date().getTime();
2568 // if the result is JSON, find the latest update-every
2569 this.data_update_every = data.view_update_every * 1000;
2570 this.data_after = data.after * 1000;
2571 this.data_before = data.before * 1000;
2572 this.netdata_first = data.first_entry * 1000;
2573 this.netdata_last = data.last_entry * 1000;
2574 this.data_points = data.points;
2577 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2578 if(this.view_after < this.data_after) {
2579 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2580 this.view_after = this.data_after;
2583 if(this.view_before > this.data_before) {
2584 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2585 this.view_before = this.data_before;
2589 this.view_after = this.data_after;
2590 this.view_before = this.data_before;
2593 if(this.debug === true) {
2594 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2596 if(this.current.force_after_ms)
2597 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2599 this.log('STATUS: forced : unset');
2601 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2602 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2603 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2604 this.log('STATUS: points : ' + (this.data_points).toString());
2607 if(this.data_points === 0) {
2612 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2613 if(this.debug === true)
2614 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2616 this.chart_created = false;
2619 // check and update the legend
2620 this.legendUpdateDOM();
2622 if(this.chart_created === true
2623 && typeof this.library.update === 'function') {
2625 if(this.debug === true)
2626 this.log('updating chart...');
2628 if(callChartLibraryUpdateSafely(data) === false)
2632 if(this.debug === true)
2633 this.log('creating chart...');
2635 if(callChartLibraryCreateSafely(data) === false)
2639 this.legendShowLatestValues();
2640 if(this.selected === true)
2641 NETDATA.globalSelectionSync.stop();
2643 // update the performance counters
2644 var now = new Date().getTime();
2645 this.tm.last_updated = now;
2647 // don't update last_autorefreshed if this chart is
2648 // forced to be updated with global PanAndZoom
2649 if(NETDATA.globalPanAndZoom.isActive())
2650 this.tm.last_autorefreshed = 0;
2652 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes)
2653 this.tm.last_autorefreshed = Math.round(now / this.data_update_every) * this.data_update_every;
2655 this.tm.last_autorefreshed = now;
2658 this.refresh_dt_ms = now - started;
2659 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2661 if(this.refresh_dt_element !== null)
2662 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2665 this.updateChart = function(callback) {
2666 if(this.debug === true)
2667 this.log('updateChart() called.');
2669 if(this._updating === true) {
2670 if(this.debug === true)
2671 this.log('I am already updating...');
2673 if(typeof callback === 'function') callback();
2677 // due to late initialization of charts and libraries
2678 // we need to check this too
2679 if(this.enabled === false) {
2680 if(this.debug === true)
2681 this.log('I am not enabled');
2683 if(typeof callback === 'function') callback();
2687 if(canBeRendered() === false) {
2688 if(typeof callback === 'function') callback();
2692 if(this.chart === null) {
2693 this.getChart(function() { that.updateChart(callback); });
2697 if(this.library.initialized === false) {
2698 if(this.library.enabled === true) {
2699 this.library.initialize(function() { that.updateChart(callback); });
2703 error('chart library "' + this.library_name + '" is not available.');
2704 if(typeof callback === 'function') callback();
2709 this.clearSelection();
2712 if(this.debug === true)
2713 this.log('updating from ' + this.data_url);
2715 this._updating = true;
2717 this.xhr = $.ajax( {
2719 crossDomain: NETDATA.options.crossDomainAjax,
2723 .success(function(data) {
2724 if(that.debug === true)
2725 that.log('data received. updating chart.');
2727 that.updateChartWithData(data);
2730 error('data download failed for url: ' + that.data_url);
2732 .always(function() {
2733 this._updating = false;
2734 if(typeof callback === 'function') callback();
2740 this.isVisible = function(nocache) {
2741 if(typeof nocache === 'undefined')
2744 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2746 // caching - we do not evaluate the charts visibility
2747 // if the page has not been scrolled since the last check
2748 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2749 return this.___isVisible___;
2751 this.tm.last_visible_check = new Date().getTime();
2753 var wh = window.innerHeight;
2754 var x = this.element.getBoundingClientRect();
2758 if(x.width === 0 || x.height === 0) {
2760 this.___isVisible___ = false;
2761 return this.___isVisible___;
2764 if(x.top < 0 && -x.top > x.height) {
2765 // the chart is entirely above
2766 ret = -x.top - x.height;
2768 else if(x.top > wh) {
2769 // the chart is entirely below
2773 if(ret > tolerance) {
2774 // the chart is too far
2777 this.___isVisible___ = false;
2778 return this.___isVisible___;
2781 // the chart is inside or very close
2784 this.___isVisible___ = true;
2785 return this.___isVisible___;
2789 this.isAutoRefreshed = function() {
2790 return (this.current.autorefresh);
2793 this.canBeAutoRefreshed = function() {
2794 now = new Date().getTime();
2796 if(this.enabled === false) {
2797 if(this.debug === true)
2798 this.log('I am not enabled');
2803 if(this.library === null || this.library.enabled === false) {
2804 error('charting library "' + this.library_name + '" is not available');
2805 if(this.debug === true)
2806 this.log('My chart library ' + this.library_name + ' is not available');
2811 if(this.isVisible() === false) {
2812 if(NETDATA.options.debug.visibility === true || this.debug === true)
2813 this.log('I am not visible');
2818 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2819 if(this.debug === true)
2820 this.log('timed force update detected - allowing this update');
2822 this.current.force_update_at = 0;
2826 if(this.isAutoRefreshed() === true) {
2827 // allow the first update, even if the page is not visible
2828 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2829 if(NETDATA.options.debug.focus === true || this.debug === true)
2830 this.log('canBeAutoRefreshed(): page does not have focus');
2835 if(this.needsRecreation() === true) {
2836 if(this.debug === true)
2837 this.log('canBeAutoRefreshed(): needs re-creation.');
2842 // options valid only for autoRefresh()
2843 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
2844 if(NETDATA.globalPanAndZoom.isActive()) {
2845 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
2846 if(this.debug === true)
2847 this.log('canBeAutoRefreshed(): global panning: I need an update.');
2852 if(this.debug === true)
2853 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
2859 if(this.selected === true) {
2860 if(this.debug === true)
2861 this.log('canBeAutoRefreshed(): I have a selection in place.');
2866 if(this.paused === true) {
2867 if(this.debug === true)
2868 this.log('canBeAutoRefreshed(): I am paused.');
2873 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
2874 if(this.debug === true)
2875 this.log('canBeAutoRefreshed(): It is time to update me.');
2885 this.autoRefresh = function(callback) {
2886 if(this.canBeAutoRefreshed() === true) {
2887 this.updateChart(callback);
2890 if(typeof callback !== 'undefined')
2895 this._defaultsFromDownloadedChart = function(chart) {
2897 this.chart_url = chart.url;
2898 this.data_update_every = chart.update_every * 1000;
2899 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2900 this.tm.last_info_downloaded = new Date().getTime();
2902 if(this.title === null)
2903 this.title = chart.title;
2905 if(this.units === null)
2906 this.units = chart.units;
2909 // fetch the chart description from the netdata server
2910 this.getChart = function(callback) {
2911 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
2913 this._defaultsFromDownloadedChart(this.chart);
2914 if(typeof callback === 'function') callback();
2917 this.chart_url = "/api/v1/chart?chart=" + this.id;
2919 if(this.debug === true)
2920 this.log('downloading ' + this.chart_url);
2923 url: this.host + this.chart_url,
2924 crossDomain: NETDATA.options.crossDomainAjax,
2928 .done(function(chart) {
2929 chart.url = that.chart_url;
2930 that._defaultsFromDownloadedChart(chart);
2931 NETDATA.chartRegistry.add(that.host, that.id, chart);
2934 NETDATA.error(404, that.chart_url);
2935 error('chart not found on url "' + that.chart_url + '"');
2937 .always(function() {
2938 if(typeof callback === 'function') callback();
2943 // ============================================================================================================
2949 NETDATA.resetAllCharts = function(state) {
2950 // first clear the global selection sync
2951 // to make sure no chart is in selected state
2952 state.globalSelectionSyncStop();
2954 // there are 2 possibilities here
2955 // a. state is the global Pan and Zoom master
2956 // b. state is not the global Pan and Zoom master
2958 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
2961 // clear the global Pan and Zoom
2962 // this will also refresh the master
2963 // and unblock any charts currently mirroring the master
2964 NETDATA.globalPanAndZoom.clearMaster();
2966 // if we were not the master, reset our status too
2967 // this is required because most probably the mouse
2968 // is over this chart, blocking it from autorefreshing
2969 if(master === false && (state.paused === true || state.selected === true))
2973 // get or create a chart state, given a DOM element
2974 NETDATA.chartState = function(element) {
2975 var state = $(element).data('netdata-state-object') || null;
2976 if(state === null) {
2977 state = new chartState(element);
2978 $(element).data('netdata-state-object', state);
2983 // ----------------------------------------------------------------------------------------------------------------
2984 // Library functions
2986 // Load a script without jquery
2987 // This is used to load jquery - after it is loaded, we use jquery
2988 NETDATA._loadjQuery = function(callback) {
2989 if(typeof jQuery === 'undefined') {
2990 if(NETDATA.options.debug.main_loop === true)
2991 console.log('loading ' + NETDATA.jQuery);
2993 var script = document.createElement('script');
2994 script.type = 'text/javascript';
2995 script.async = true;
2996 script.src = NETDATA.jQuery;
2998 // script.onabort = onError;
2999 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3000 if(typeof callback === "function")
3001 script.onload = callback;
3003 var s = document.getElementsByTagName('script')[0];
3004 s.parentNode.insertBefore(script, s);
3006 else if(typeof callback === "function")
3010 NETDATA._loadCSS = function(filename) {
3011 // don't use jQuery here
3012 // styles are loaded before jQuery
3013 // to eliminate showing an unstyled page to the user
3015 var fileref = document.createElement("link");
3016 fileref.setAttribute("rel", "stylesheet");
3017 fileref.setAttribute("type", "text/css");
3018 fileref.setAttribute("href", filename);
3020 if (typeof fileref !== 'undefined')
3021 document.getElementsByTagName("head")[0].appendChild(fileref);
3024 NETDATA.colorHex2Rgb = function(hex) {
3025 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3026 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3027 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3028 return r + r + g + g + b + b;
3031 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3033 r: parseInt(result[1], 16),
3034 g: parseInt(result[2], 16),
3035 b: parseInt(result[3], 16)
3039 NETDATA.colorLuminance = function(hex, lum) {
3040 // validate hex string
3041 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3043 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3047 // convert to decimal and change luminosity
3048 var rgb = "#", c, i;
3049 for (i = 0; i < 3; i++) {
3050 c = parseInt(hex.substr(i*2,2), 16);
3051 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3052 rgb += ("00"+c).substr(c.length);
3058 NETDATA.guid = function() {
3060 return Math.floor((1 + Math.random()) * 0x10000)
3065 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3068 NETDATA.zeropad = function(x) {
3069 if(x > -10 && x < 10) return '0' + x.toString();
3070 else return x.toString();
3073 // user function to signal us the DOM has been
3075 NETDATA.updatedDom = function() {
3076 NETDATA.options.updated_dom = true;
3079 NETDATA.ready = function(callback) {
3080 NETDATA.options.pauseCallback = callback;
3083 NETDATA.pause = function(callback) {
3084 if(NETDATA.options.pause === true)
3087 NETDATA.options.pauseCallback = callback;
3090 NETDATA.unpause = function() {
3091 NETDATA.options.pauseCallback = null;
3092 NETDATA.options.updated_dom = true;
3093 NETDATA.options.pause = false;
3096 // ----------------------------------------------------------------------------------------------------------------
3098 // this is purely sequencial charts refresher
3099 // it is meant to be autonomous
3100 NETDATA.chartRefresherNoParallel = function(index) {
3101 if(NETDATA.options.debug.mail_loop === true)
3102 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3104 if(NETDATA.options.updated_dom === true) {
3105 // the dom has been updated
3106 // get the dom parts again
3107 NETDATA.parseDom(NETDATA.chartRefresher);
3110 if(index >= NETDATA.options.targets.length) {
3111 if(NETDATA.options.debug.main_loop === true)
3112 console.log('waiting to restart main loop...');
3114 NETDATA.options.auto_refresher_fast_weight = 0;
3116 setTimeout(function() {
3117 NETDATA.chartRefresher();
3118 }, NETDATA.options.current.idle_between_loops);
3121 var state = NETDATA.options.targets[index];
3123 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3124 if(NETDATA.options.debug.main_loop === true)
3125 console.log('fast rendering...');
3127 state.autoRefresh(function() {
3128 NETDATA.chartRefresherNoParallel(++index);
3132 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3133 NETDATA.options.auto_refresher_fast_weight = 0;
3135 setTimeout(function() {
3136 state.autoRefresh(function() {
3137 NETDATA.chartRefresherNoParallel(++index);
3139 }, NETDATA.options.current.idle_between_charts);
3144 // this is part of the parallel refresher
3145 // its cause is to refresh sequencially all the charts
3146 // that depend on chart library initialization
3147 // it will call the parallel refresher back
3148 // as soon as it sees a chart that its chart library
3150 NETDATA.chartRefresher_unitialized = function() {
3151 if(NETDATA.options.updated_dom === true) {
3152 // the dom has been updated
3153 // get the dom parts again
3154 NETDATA.parseDom(NETDATA.chartRefresher);
3158 if(NETDATA.options.sequencial.length === 0)
3159 NETDATA.chartRefresher();
3161 var state = NETDATA.options.sequencial.pop();
3162 if(state.library.initialized === true)
3163 NETDATA.chartRefresher();
3165 state.autoRefresh(NETDATA.chartRefresher_unitialized);
3169 NETDATA.chartRefresherWaitTime = function() {
3170 return NETDATA.options.current.idle_parallel_loops;
3173 // the default refresher
3174 // it will create 2 sets of charts:
3175 // - the ones that can be refreshed in parallel
3176 // - the ones that depend on something else
3177 // the first set will be executed in parallel
3178 // the second will be given to NETDATA.chartRefresher_unitialized()
3179 NETDATA.chartRefresher = function() {
3180 if(NETDATA.options.pause === true) {
3181 // console.log('auto-refresher is paused');
3182 setTimeout(NETDATA.chartRefresher,
3183 NETDATA.chartRefresherWaitTime());
3187 if(typeof NETDATA.options.pauseCallback === 'function') {
3188 // console.log('auto-refresher is calling pauseCallback');
3189 NETDATA.options.pause = true;
3190 NETDATA.options.pauseCallback();
3191 NETDATA.chartRefresher();
3195 if(NETDATA.options.current.parallel_refresher === false) {
3196 NETDATA.chartRefresherNoParallel(0);
3200 if(NETDATA.options.updated_dom === true) {
3201 // the dom has been updated
3202 // get the dom parts again
3203 NETDATA.parseDom(NETDATA.chartRefresher);
3207 var parallel = new Array();
3208 var targets = NETDATA.options.targets;
3209 var len = targets.length;
3211 if(targets[len].isVisible() === false)
3214 var state = targets[len];
3215 if(state.library.initialized === false) {
3216 if(state.library.enabled === true) {
3217 state.library.initialize(NETDATA.chartRefresher);
3221 state.error('chart library "' + state.library_name + '" is not enabled.');
3225 parallel.unshift(state);
3228 if(parallel.length > 0) {
3229 var parallel_jobs = parallel.length;
3231 // this will execute the jobs in parallel
3232 $(parallel).each(function() {
3233 this.autoRefresh(function() {
3236 if(parallel_jobs === 0) {
3237 setTimeout(NETDATA.chartRefresher,
3238 NETDATA.chartRefresherWaitTime());
3244 setTimeout(NETDATA.chartRefresher,
3245 NETDATA.chartRefresherWaitTime());
3249 NETDATA.parseDom = function(callback) {
3250 NETDATA.options.last_page_scroll = new Date().getTime();
3251 NETDATA.options.updated_dom = false;
3253 var targets = $('div[data-netdata]'); //.filter(':visible');
3255 if(NETDATA.options.debug.main_loop === true)
3256 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3258 NETDATA.options.targets = new Array();
3259 var len = targets.length;
3261 // the initialization will take care of sizing
3262 // and the "loading..." message
3263 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3266 if(typeof callback === 'function') callback();
3269 // this is the main function - where everything starts
3270 NETDATA.start = function() {
3271 // this should be called only once
3273 NETDATA.options.page_is_visible = true;
3275 $(window).blur(function() {
3276 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3277 NETDATA.options.page_is_visible = false;
3278 if(NETDATA.options.debug.focus === true)
3279 console.log('Lost Focus!');
3283 $(window).focus(function() {
3284 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3285 NETDATA.options.page_is_visible = true;
3286 if(NETDATA.options.debug.focus === true)
3287 console.log('Focus restored!');
3291 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3292 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3293 NETDATA.options.page_is_visible = false;
3294 if(NETDATA.options.debug.focus === true)
3295 console.log('Document has no focus!');
3299 // bootstrap tab switching
3300 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3302 // bootstrap modal switching
3303 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3304 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3306 NETDATA.parseDom(NETDATA.chartRefresher);
3309 // ----------------------------------------------------------------------------------------------------------------
3312 NETDATA.peityInitialize = function(callback) {
3313 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3315 url: NETDATA.peity_js,
3320 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3323 NETDATA.chartLibraries.peity.enabled = false;
3324 NETDATA.error(100, NETDATA.peity_js);
3326 .always(function() {
3327 if(typeof callback === "function")
3332 NETDATA.chartLibraries.peity.enabled = false;
3333 if(typeof callback === "function")
3338 NETDATA.peityChartUpdate = function(state, data) {
3339 state.peity_instance.innerHTML = data.result;
3341 if(state.peity_options.stroke !== state.chartColors()[0]) {
3342 state.peity_options.stroke = state.chartColors()[0];
3343 if(state.chart.chart_type === 'line')
3344 state.peity_options.fill = NETDATA.themes.current.background;
3346 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3349 $(state.peity_instance).peity('line', state.peity_options);
3353 NETDATA.peityChartCreate = function(state, data) {
3354 state.peity_instance = document.createElement('div');
3355 state.element_chart.appendChild(state.peity_instance);
3357 var self = $(state.element);
3358 state.peity_options = {
3359 stroke: NETDATA.themes.current.foreground,
3360 strokeWidth: self.data('peity-strokewidth') || 1,
3361 width: state.chartWidth(),
3362 height: state.chartHeight(),
3363 fill: NETDATA.themes.current.foreground
3366 NETDATA.peityChartUpdate(state, data);
3370 // ----------------------------------------------------------------------------------------------------------------
3373 NETDATA.sparklineInitialize = function(callback) {
3374 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3376 url: NETDATA.sparkline_js,
3381 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3384 NETDATA.chartLibraries.sparkline.enabled = false;
3385 NETDATA.error(100, NETDATA.sparkline_js);
3387 .always(function() {
3388 if(typeof callback === "function")
3393 NETDATA.chartLibraries.sparkline.enabled = false;
3394 if(typeof callback === "function")
3399 NETDATA.sparklineChartUpdate = function(state, data) {
3400 state.sparkline_options.width = state.chartWidth();
3401 state.sparkline_options.height = state.chartHeight();
3403 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3407 NETDATA.sparklineChartCreate = function(state, data) {
3408 var self = $(state.element);
3409 var type = self.data('sparkline-type') || 'line';
3410 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3411 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3412 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3413 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3414 var composite = self.data('sparkline-composite') || undefined;
3415 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3416 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3417 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3418 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3419 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3420 var spotColor = self.data('sparkline-spotcolor') || undefined;
3421 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3422 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3423 var spotRadius = self.data('sparkline-spotradius') || undefined;
3424 var valueSpots = self.data('sparkline-valuespots') || undefined;
3425 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3426 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3427 var lineWidth = self.data('sparkline-linewidth') || undefined;
3428 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3429 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3430 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3431 var xvalues = self.data('sparkline-xvalues') || undefined;
3432 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3433 var xvalues = self.data('sparkline-xvalues') || undefined;
3434 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3435 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3436 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3437 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3438 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3439 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3440 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3441 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3442 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3443 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3444 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3445 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3446 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3447 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3448 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3449 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3450 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3451 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3452 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3453 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3454 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3456 state.sparkline_options = {
3458 lineColor: lineColor,
3459 fillColor: fillColor,
3460 chartRangeMin: chartRangeMin,
3461 chartRangeMax: chartRangeMax,
3462 composite: composite,
3463 enableTagOptions: enableTagOptions,
3464 tagOptionPrefix: tagOptionPrefix,
3465 tagValuesAttribute: tagValuesAttribute,
3466 disableHiddenCheck: disableHiddenCheck,
3467 defaultPixelsPerValue: defaultPixelsPerValue,
3468 spotColor: spotColor,
3469 minSpotColor: minSpotColor,
3470 maxSpotColor: maxSpotColor,
3471 spotRadius: spotRadius,
3472 valueSpots: valueSpots,
3473 highlightSpotColor: highlightSpotColor,
3474 highlightLineColor: highlightLineColor,
3475 lineWidth: lineWidth,
3476 normalRangeMin: normalRangeMin,
3477 normalRangeMax: normalRangeMax,
3478 drawNormalOnTop: drawNormalOnTop,
3480 chartRangeClip: chartRangeClip,
3481 chartRangeMinX: chartRangeMinX,
3482 chartRangeMaxX: chartRangeMaxX,
3483 disableInteraction: disableInteraction,
3484 disableTooltips: disableTooltips,
3485 disableHighlight: disableHighlight,
3486 highlightLighten: highlightLighten,
3487 highlightColor: highlightColor,
3488 tooltipContainer: tooltipContainer,
3489 tooltipClassname: tooltipClassname,
3490 tooltipChartTitle: state.title,
3491 tooltipFormat: tooltipFormat,
3492 tooltipPrefix: tooltipPrefix,
3493 tooltipSuffix: tooltipSuffix,
3494 tooltipSkipNull: tooltipSkipNull,
3495 tooltipValueLookups: tooltipValueLookups,
3496 tooltipFormatFieldlist: tooltipFormatFieldlist,
3497 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3498 numberFormatter: numberFormatter,
3499 numberDigitGroupSep: numberDigitGroupSep,
3500 numberDecimalMark: numberDecimalMark,
3501 numberDigitGroupCount: numberDigitGroupCount,
3502 animatedZooms: animatedZooms,
3503 width: state.chartWidth(),
3504 height: state.chartHeight()
3507 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3511 // ----------------------------------------------------------------------------------------------------------------
3518 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3519 if(after < state.netdata_first)
3520 after = state.netdata_first;
3522 if(before > state.netdata_last)
3523 before = state.netdata_last;
3525 state.setMode('zoom');
3526 state.globalSelectionSyncStop();
3527 state.globalSelectionSyncDelay();
3528 state.dygraph_user_action = true;
3529 state.dygraph_force_zoom = true;
3530 state.updateChartPanOrZoom(after, before);
3531 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3534 NETDATA.dygraphSetSelection = function(state, t) {
3535 if(typeof state.dygraph_instance !== 'undefined') {
3536 var r = state.calculateRowForTime(t);
3538 state.dygraph_instance.setSelection(r);
3540 state.dygraph_instance.clearSelection();
3541 state.legendShowUndefined();
3548 NETDATA.dygraphClearSelection = function(state, t) {
3549 if(typeof state.dygraph_instance !== 'undefined') {
3550 state.dygraph_instance.clearSelection();
3555 NETDATA.dygraphSmoothInitialize = function(callback) {
3557 url: NETDATA.dygraph_smooth_js,
3562 NETDATA.dygraph.smooth = true;
3563 smoothPlotter.smoothing = 0.3;
3566 NETDATA.dygraph.smooth = false;
3568 .always(function() {
3569 if(typeof callback === "function")
3574 NETDATA.dygraphInitialize = function(callback) {
3575 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3577 url: NETDATA.dygraph_js,
3582 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3585 NETDATA.chartLibraries.dygraph.enabled = false;
3586 NETDATA.error(100, NETDATA.dygraph_js);
3588 .always(function() {
3589 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3590 NETDATA.dygraphSmoothInitialize(callback);
3591 else if(typeof callback === "function")
3596 NETDATA.chartLibraries.dygraph.enabled = false;
3597 if(typeof callback === "function")
3602 NETDATA.dygraphChartUpdate = function(state, data) {
3603 var dygraph = state.dygraph_instance;
3605 if(typeof dygraph === 'undefined')
3606 return NETDATA.dygraphChartCreate(state, data);
3608 // when the chart is not visible, and hidden
3609 // if there is a window resize, dygraph detects
3610 // its element size as 0x0.
3611 // this will make it re-appear properly
3613 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3617 file: data.result.data,
3618 colors: state.chartColors(),
3619 labels: data.result.labels,
3620 labelsDivWidth: state.chartWidth() - 70,
3621 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3624 if(state.dygraph_force_zoom === true) {
3625 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3626 state.log('dygraphChartUpdate() forced zoom update');
3628 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3629 options.valueRange = null;
3630 options.isZoomedIgnoreProgrammaticZoom = true;
3631 state.dygraph_force_zoom = false;
3633 else if(state.current.name !== 'auto') {
3634 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3635 state.log('dygraphChartUpdate() loose update');
3638 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3639 state.log('dygraphChartUpdate() strict update');
3641 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3642 options.valueRange = null;
3643 options.isZoomedIgnoreProgrammaticZoom = true;
3646 if(state.dygraph_smooth_eligible === true) {
3647 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3648 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3649 NETDATA.dygraphChartCreate(state, data);
3654 dygraph.updateOptions(options);
3656 state.dygraph_last_rendered = new Date().getTime();
3660 NETDATA.dygraphChartCreate = function(state, data) {
3661 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3662 state.log('dygraphChartCreate()');
3664 var self = $(state.element);
3666 var chart_type = state.chart.chart_type;
3667 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3668 chart_type = self.data('dygraph-type') || chart_type;
3670 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3671 smooth = self.data('dygraph-smooth') || smooth;
3673 if(NETDATA.dygraph.smooth === false)
3676 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3677 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3679 state.dygraph_options = {
3680 colors: self.data('dygraph-colors') || state.chartColors(),
3682 // leave a few pixels empty on the right of the chart
3683 rightGap: self.data('dygraph-rightgap') || 5,
3684 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3685 showRoller: self.data('dygraph-showroller') || false,
3687 title: self.data('dygraph-title') || state.title,
3688 titleHeight: self.data('dygraph-titleheight') || 19,
3690 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3691 labels: data.result.labels,
3692 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3693 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3694 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3695 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3696 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3699 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3700 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3702 ylabel: state.units,
3703 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3705 // the function to plot the chart
3708 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3709 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3710 strokePattern: self.data('dygraph-strokepattern') || undefined,
3712 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3713 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3714 drawPoints: self.data('dygraph-drawpoints') || false,
3716 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3717 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3719 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3720 pointSize: self.data('dygraph-pointsize') || 1,
3722 // enabling this makes the chart with little square lines
3723 stepPlot: self.data('dygraph-stepplot') || false,
3725 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3726 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3727 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3729 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3730 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3731 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3732 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3734 drawAxis: self.data('dygraph-drawaxis') || true,
3735 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3736 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3737 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3739 drawGrid: self.data('dygraph-drawgrid') || true,
3740 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3741 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3742 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3743 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3744 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3746 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3747 sigFigs: self.data('dygraph-sigfigs') || null,
3748 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3749 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3751 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3752 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3753 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3755 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3756 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3760 ticker: Dygraph.dateTicker,
3761 axisLabelFormatter: function (d, gran) {
3762 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3764 valueFormatter: function (ms) {
3765 var d = new Date(ms);
3766 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3767 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3772 valueFormatter: function (x) {
3773 // we format legends with the state object
3774 // no need to do anything here
3775 // return (Math.round(x*100) / 100).toLocaleString();
3776 // return state.legendFormatValue(x);
3781 legendFormatter: function(data) {
3782 var elements = state.element_legend_childs;
3784 // if the hidden div is not there
3785 // we are not managing the legend
3786 if(elements.hidden === null) return;
3788 if (typeof data.x !== 'undefined') {
3789 state.legendSetDate(data.x);
3790 var i = data.series.length;
3792 var series = data.series[i];
3793 if(!series.isVisible) continue;
3794 state.legendSetLabelValue(series.label, series.y);
3800 drawCallback: function(dygraph, is_initial) {
3801 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3802 state.dygraph_user_action = false;
3804 var x_range = dygraph.xAxisRange();
3805 var after = Math.round(x_range[0]);
3806 var before = Math.round(x_range[1]);
3808 if(NETDATA.options.debug.dygraph === true)
3809 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3811 if(before <= state.netdata_last && after >= state.netdata_first)
3812 state.updateChartPanOrZoom(after, before);
3815 zoomCallback: function(minDate, maxDate, yRanges) {
3816 if(NETDATA.options.debug.dygraph === true)
3817 state.log('dygraphZoomCallback()');
3819 state.globalSelectionSyncStop();
3820 state.globalSelectionSyncDelay();
3821 state.setMode('zoom');
3823 // refresh it to the greatest possible zoom level
3824 state.dygraph_user_action = true;
3825 state.dygraph_force_zoom = true;
3826 state.updateChartPanOrZoom(minDate, maxDate);
3828 highlightCallback: function(event, x, points, row, seriesName) {
3829 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3830 state.log('dygraphHighlightCallback()');
3834 // there is a bug in dygraph when the chart is zoomed enough
3835 // the time it thinks is selected is wrong
3836 // here we calculate the time t based on the row number selected
3838 var t = state.data_after + row * state.data_update_every;
3839 // 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);
3841 state.globalSelectionSync(x);
3843 // fix legend zIndex using the internal structures of dygraph legend module
3844 // this works, but it is a hack!
3845 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
3847 unhighlightCallback: function(event) {
3848 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3849 state.log('dygraphUnhighlightCallback()');
3851 state.unpauseChart();
3852 state.globalSelectionSyncStop();
3854 interactionModel : {
3855 mousedown: function(event, dygraph, context) {
3856 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3857 state.log('interactionModel.mousedown()');
3859 state.dygraph_user_action = true;
3860 state.globalSelectionSyncStop();
3862 if(NETDATA.options.debug.dygraph === true)
3863 state.log('dygraphMouseDown()');
3865 // Right-click should not initiate a zoom.
3866 if(event.button && event.button === 2) return;
3868 context.initializeMouseDown(event, dygraph, context);
3870 if(event.button && event.button === 1) {
3871 if (event.altKey || event.shiftKey) {
3872 state.setMode('pan');
3873 state.globalSelectionSyncDelay();
3874 Dygraph.startPan(event, dygraph, context);
3877 state.setMode('zoom');
3878 state.globalSelectionSyncDelay();
3879 Dygraph.startZoom(event, dygraph, context);
3883 if (event.altKey || event.shiftKey) {
3884 state.setMode('zoom');
3885 state.globalSelectionSyncDelay();
3886 Dygraph.startZoom(event, dygraph, context);
3889 state.setMode('pan');
3890 state.globalSelectionSyncDelay();
3891 Dygraph.startPan(event, dygraph, context);
3895 mousemove: function(event, dygraph, context) {
3896 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3897 state.log('interactionModel.mousemove()');
3899 if(context.isPanning) {
3900 state.dygraph_user_action = true;
3901 state.globalSelectionSyncStop();
3902 state.globalSelectionSyncDelay();
3903 state.setMode('pan');
3904 Dygraph.movePan(event, dygraph, context);
3906 else if(context.isZooming) {
3907 state.dygraph_user_action = true;
3908 state.globalSelectionSyncStop();
3909 state.globalSelectionSyncDelay();
3910 state.setMode('zoom');
3911 Dygraph.moveZoom(event, dygraph, context);
3914 mouseup: function(event, dygraph, context) {
3915 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3916 state.log('interactionModel.mouseup()');
3918 if (context.isPanning) {
3919 state.dygraph_user_action = true;
3920 state.globalSelectionSyncDelay();
3921 Dygraph.endPan(event, dygraph, context);
3923 else if (context.isZooming) {
3924 state.dygraph_user_action = true;
3925 state.globalSelectionSyncDelay();
3926 Dygraph.endZoom(event, dygraph, context);
3929 click: function(event, dygraph, context) {
3930 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3931 state.log('interactionModel.click()');
3933 event.preventDefault();
3935 dblclick: function(event, dygraph, context) {
3936 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3937 state.log('interactionModel.dblclick()');
3938 NETDATA.resetAllCharts(state);
3940 mousewheel: function(event, dygraph, context) {
3941 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3942 state.log('interactionModel.mousewheel()');
3944 // Take the offset of a mouse event on the dygraph canvas and
3945 // convert it to a pair of percentages from the bottom left.
3946 // (Not top left, bottom is where the lower value is.)
3947 function offsetToPercentage(g, offsetX, offsetY) {
3948 // This is calculating the pixel offset of the leftmost date.
3949 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
3950 var yar0 = g.yAxisRange(0);
3952 // This is calculating the pixel of the higest value. (Top pixel)
3953 var yOffset = g.toDomCoords(null, yar0[1])[1];
3955 // x y w and h are relative to the corner of the drawing area,
3956 // so that the upper corner of the drawing area is (0, 0).
3957 var x = offsetX - xOffset;
3958 var y = offsetY - yOffset;
3960 // This is computing the rightmost pixel, effectively defining the
3962 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
3964 // This is computing the lowest pixel, effectively defining the height.
3965 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
3967 // Percentage from the left.
3968 var xPct = w === 0 ? 0 : (x / w);
3969 // Percentage from the top.
3970 var yPct = h === 0 ? 0 : (y / h);
3972 // The (1-) part below changes it from "% distance down from the top"
3973 // to "% distance up from the bottom".
3974 return [xPct, (1-yPct)];
3977 // Adjusts [x, y] toward each other by zoomInPercentage%
3978 // Split it so the left/bottom axis gets xBias/yBias of that change and
3979 // tight/top gets (1-xBias)/(1-yBias) of that change.
3981 // If a bias is missing it splits it down the middle.
3982 function zoomRange(g, zoomInPercentage, xBias, yBias) {
3983 xBias = xBias || 0.5;
3984 yBias = yBias || 0.5;
3986 function adjustAxis(axis, zoomInPercentage, bias) {
3987 var delta = axis[1] - axis[0];
3988 var increment = delta * zoomInPercentage;
3989 var foo = [increment * bias, increment * (1-bias)];
3991 return [ axis[0] + foo[0], axis[1] - foo[1] ];
3994 var yAxes = g.yAxisRanges();
3996 for (var i = 0; i < yAxes.length; i++) {
3997 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4000 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4003 if(event.altKey || event.shiftKey) {
4004 state.dygraph_user_action = true;
4006 state.globalSelectionSyncStop();
4007 state.globalSelectionSyncDelay();
4009 // http://dygraphs.com/gallery/interaction-api.js
4010 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4011 var percentage = normal / 50;
4013 if (!(event.offsetX && event.offsetY)){
4014 event.offsetX = event.layerX - event.target.offsetLeft;
4015 event.offsetY = event.layerY - event.target.offsetTop;
4018 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4019 var xPct = percentages[0];
4020 var yPct = percentages[1];
4022 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4024 var after = new_x_range[0];
4025 var before = new_x_range[1];
4027 var first = state.netdata_first + state.data_update_every;
4028 var last = state.netdata_last + state.data_update_every;
4031 after -= (before - last);
4038 state.setMode('zoom');
4039 if(state.updateChartPanOrZoom(after, before) === true)
4040 dygraph.updateOptions({ dateWindow: [ after, before ] });
4042 event.preventDefault();
4045 touchstart: function(event, dygraph, context) {
4046 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4047 state.log('interactionModel.touchstart()');
4049 state.dygraph_user_action = true;
4050 state.setMode('zoom');
4053 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4055 // we overwrite the touch directions at the end, to overwrite
4056 // the internal default of dygraphs
4057 context.touchDirections = { x: true, y: false };
4059 state.dygraph_last_touch_start = new Date().getTime();
4060 state.dygraph_last_touch_move = 0;
4062 if(typeof event.touches[0].pageX === 'number')
4063 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4065 state.dygraph_last_touch_page_x = 0;
4067 touchmove: function(event, dygraph, context) {
4068 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4069 state.log('interactionModel.touchmove()');
4071 state.dygraph_user_action = true;
4072 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4074 state.dygraph_last_touch_move = new Date().getTime();
4076 touchend: function(event, dygraph, context) {
4077 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4078 state.log('interactionModel.touchend()');
4080 state.dygraph_user_action = true;
4081 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4083 // if it didn't move, it is a selection
4084 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4085 // internal api of dygraphs
4086 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4087 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4088 if(NETDATA.dygraphSetSelection(state, t) === true)
4089 state.globalSelectionSync(t);
4092 // if it was double tap within double click time, reset the charts
4093 var now = new Date().getTime();
4094 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4095 if(state.dygraph_last_touch_move === 0) {
4096 var dt = now - state.dygraph_last_touch_end;
4097 if(dt <= NETDATA.options.current.double_click_speed)
4098 NETDATA.resetAllCharts(state);
4102 // remember the timestamp of the last touch end
4103 state.dygraph_last_touch_end = now;
4108 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4109 state.dygraph_options.drawGrid = false;
4110 state.dygraph_options.drawAxis = false;
4111 state.dygraph_options.title = undefined;
4112 state.dygraph_options.units = undefined;
4113 state.dygraph_options.ylabel = undefined;
4114 state.dygraph_options.yLabelWidth = 0;
4115 state.dygraph_options.labelsDivWidth = 120;
4116 state.dygraph_options.labelsDivStyles.width = '120px';
4117 state.dygraph_options.labelsSeparateLines = true;
4118 state.dygraph_options.rightGap = 0;
4121 if(smooth === true) {
4122 state.dygraph_smooth_eligible = true;
4124 if(NETDATA.options.current.smooth_plot === true)
4125 state.dygraph_options.plotter = smoothPlotter;
4127 else state.dygraph_smooth_eligible = false;
4129 state.dygraph_instance = new Dygraph(state.element_chart,
4130 data.result.data, state.dygraph_options);
4132 state.dygraph_force_zoom = false;
4133 state.dygraph_user_action = false;
4134 state.dygraph_last_rendered = new Date().getTime();
4138 // ----------------------------------------------------------------------------------------------------------------
4141 NETDATA.morrisInitialize = function(callback) {
4142 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4144 // morris requires raphael
4145 if(!NETDATA.chartLibraries.raphael.initialized) {
4146 if(NETDATA.chartLibraries.raphael.enabled) {
4147 NETDATA.raphaelInitialize(function() {
4148 NETDATA.morrisInitialize(callback);
4152 NETDATA.chartLibraries.morris.enabled = false;
4153 if(typeof callback === "function")
4158 NETDATA._loadCSS(NETDATA.morris_css);
4161 url: NETDATA.morris_js,
4166 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4169 NETDATA.chartLibraries.morris.enabled = false;
4170 NETDATA.error(100, NETDATA.morris_js);
4172 .always(function() {
4173 if(typeof callback === "function")
4179 NETDATA.chartLibraries.morris.enabled = false;
4180 if(typeof callback === "function")
4185 NETDATA.morrisChartUpdate = function(state, data) {
4186 state.morris_instance.setData(data.result.data);
4190 NETDATA.morrisChartCreate = function(state, data) {
4192 state.morris_options = {
4193 element: state.element_chart.id,
4194 data: data.result.data,
4196 ykeys: data.dimension_names,
4197 labels: data.dimension_names,
4203 continuousLine: false,
4204 behaveLikeLine: false
4207 if(state.chart.chart_type === 'line')
4208 state.morris_instance = new Morris.Line(state.morris_options);
4210 else if(state.chart.chart_type === 'area') {
4211 state.morris_options.behaveLikeLine = true;
4212 state.morris_instance = new Morris.Area(state.morris_options);
4215 state.morris_instance = new Morris.Area(state.morris_options);
4220 // ----------------------------------------------------------------------------------------------------------------
4223 NETDATA.raphaelInitialize = function(callback) {
4224 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4226 url: NETDATA.raphael_js,
4231 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4234 NETDATA.chartLibraries.raphael.enabled = false;
4235 NETDATA.error(100, NETDATA.raphael_js);
4237 .always(function() {
4238 if(typeof callback === "function")
4243 NETDATA.chartLibraries.raphael.enabled = false;
4244 if(typeof callback === "function")
4249 NETDATA.raphaelChartUpdate = function(state, data) {
4250 $(state.element_chart).raphael(data.result, {
4251 width: state.chartWidth(),
4252 height: state.chartHeight()
4258 NETDATA.raphaelChartCreate = function(state, data) {
4259 $(state.element_chart).raphael(data.result, {
4260 width: state.chartWidth(),
4261 height: state.chartHeight()
4267 // ----------------------------------------------------------------------------------------------------------------
4270 NETDATA.c3Initialize = function(callback) {
4271 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4274 if(!NETDATA.chartLibraries.d3.initialized) {
4275 if(NETDATA.chartLibraries.d3.enabled) {
4276 NETDATA.d3Initialize(function() {
4277 NETDATA.c3Initialize(callback);
4281 NETDATA.chartLibraries.c3.enabled = false;
4282 if(typeof callback === "function")
4287 NETDATA._loadCSS(NETDATA.c3_css);
4295 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4298 NETDATA.chartLibraries.c3.enabled = false;
4299 NETDATA.error(100, NETDATA.c3_js);
4301 .always(function() {
4302 if(typeof callback === "function")
4308 NETDATA.chartLibraries.c3.enabled = false;
4309 if(typeof callback === "function")
4314 NETDATA.c3ChartUpdate = function(state, data) {
4315 state.c3_instance.destroy();
4316 return NETDATA.c3ChartCreate(state, data);
4318 //state.c3_instance.load({
4319 // rows: data.result,
4326 NETDATA.c3ChartCreate = function(state, data) {
4328 state.element_chart.id = 'c3-' + state.uuid;
4329 // console.log('id = ' + state.element_chart.id);
4331 state.c3_instance = c3.generate({
4332 bindto: '#' + state.element_chart.id,
4334 width: state.chartWidth(),
4335 height: state.chartHeight()
4338 pattern: state.chartColors()
4343 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4349 format: function(x) {
4350 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4377 // console.log(state.c3_instance);
4382 // ----------------------------------------------------------------------------------------------------------------
4385 NETDATA.d3Initialize = function(callback) {
4386 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4393 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4396 NETDATA.chartLibraries.d3.enabled = false;
4397 NETDATA.error(100, NETDATA.d3_js);
4399 .always(function() {
4400 if(typeof callback === "function")
4405 NETDATA.chartLibraries.d3.enabled = false;
4406 if(typeof callback === "function")
4411 NETDATA.d3ChartUpdate = function(state, data) {
4415 NETDATA.d3ChartCreate = function(state, data) {
4419 // ----------------------------------------------------------------------------------------------------------------
4422 NETDATA.googleInitialize = function(callback) {
4423 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4425 url: NETDATA.google_js,
4430 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4431 google.load('visualization', '1.1', {
4432 'packages': ['corechart', 'controls'],
4433 'callback': callback
4437 NETDATA.chartLibraries.google.enabled = false;
4438 NETDATA.error(100, NETDATA.google_js);
4439 if(typeof callback === "function")
4444 NETDATA.chartLibraries.google.enabled = false;
4445 if(typeof callback === "function")
4450 NETDATA.googleChartUpdate = function(state, data) {
4451 var datatable = new google.visualization.DataTable(data.result);
4452 state.google_instance.draw(datatable, state.google_options);
4456 NETDATA.googleChartCreate = function(state, data) {
4457 var datatable = new google.visualization.DataTable(data.result);
4459 state.google_options = {
4460 colors: state.chartColors(),
4462 // do not set width, height - the chart resizes itself
4463 //width: state.chartWidth(),
4464 //height: state.chartHeight(),
4469 // title: "Time of Day",
4470 // format:'HH:mm:ss',
4471 viewWindowMode: 'maximized',
4483 viewWindowMode: 'pretty',
4498 focusTarget: 'category',
4505 titlePosition: 'out',
4516 curveType: 'function',
4521 switch(state.chart.chart_type) {
4523 state.google_options.vAxis.viewWindowMode = 'maximized';
4524 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4525 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4529 state.google_options.isStacked = true;
4530 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4531 state.google_options.vAxis.viewWindowMode = 'maximized';
4532 state.google_options.vAxis.minValue = null;
4533 state.google_options.vAxis.maxValue = null;
4534 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4539 state.google_options.lineWidth = 2;
4540 state.google_instance = new google.visualization.LineChart(state.element_chart);
4544 state.google_instance.draw(datatable, state.google_options);
4548 // ----------------------------------------------------------------------------------------------------------------
4550 NETDATA.percentFromValueMax = function(value, max) {
4551 if(value === null) value = 0;
4552 if(max < value) max = value;
4556 pcent = Math.round(value * 100 / max);
4557 if(pcent === 0 && value > 0) pcent = 1;
4563 // ----------------------------------------------------------------------------------------------------------------
4566 NETDATA.easypiechartInitialize = function(callback) {
4567 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4569 url: NETDATA.easypiechart_js,
4574 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4577 NETDATA.chartLibraries.easypiechart.enabled = false;
4578 NETDATA.error(100, NETDATA.easypiechart_js);
4580 .always(function() {
4581 if(typeof callback === "function")
4586 NETDATA.chartLibraries.easypiechart.enabled = false;
4587 if(typeof callback === "function")
4592 NETDATA.easypiechartClearSelection = function(state) {
4593 if(typeof state.easyPieChartEvent !== 'undefined') {
4594 if(state.easyPieChartEvent.timer !== null)
4595 clearTimeout(state.easyPieChartEvent.timer);
4597 state.easyPieChartEvent.timer = null;
4600 if(state.isAutoRefreshed() === true && state.data !== null) {
4601 NETDATA.easypiechartChartUpdate(state, state.data);
4604 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4605 state.easyPieChart_instance.update(0);
4607 state.easyPieChart_instance.enableAnimation();
4612 NETDATA.easypiechartSetSelection = function(state, t) {
4613 if(state.timeIsVisible(t) !== true)
4614 return NETDATA.easypiechartClearSelection(state);
4616 var slot = state.calculateRowForTime(t);
4617 if(slot < 0 || slot >= state.data.result.length)
4618 return NETDATA.easypiechartClearSelection(state);
4620 if(typeof state.easyPieChartEvent === 'undefined') {
4621 state.easyPieChartEvent = {
4628 var value = state.data.result[state.data.result.length - 1 - slot];
4629 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4630 var pcent = NETDATA.percentFromValueMax(value, max);
4632 state.easyPieChartEvent.value = value;
4633 state.easyPieChartEvent.pcent = pcent;
4634 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4636 if(state.easyPieChartEvent.timer === null) {
4637 state.easyPieChart_instance.disableAnimation();
4639 state.easyPieChartEvent.timer = setTimeout(function() {
4640 state.easyPieChartEvent.timer = null;
4641 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4642 }, NETDATA.options.current.charts_selection_animation_delay);
4648 NETDATA.easypiechartChartUpdate = function(state, data) {
4649 var value, max, pcent;
4651 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
4657 value = data.result[0];
4658 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4659 pcent = NETDATA.percentFromValueMax(value, max);
4662 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4663 state.easyPieChart_instance.update(pcent);
4667 NETDATA.easypiechartChartCreate = function(state, data) {
4668 var self = $(state.element);
4669 var chart = $(state.element_chart);
4671 var value = data.result[0];
4672 var max = self.data('easypiechart-max-value') || null;
4673 var adjust = self.data('easypiechart-adjust') || null;
4677 state.easyPieChartMax = null;
4680 state.easyPieChartMax = max;
4682 var pcent = NETDATA.percentFromValueMax(value, max);
4684 chart.data('data-percent', pcent);
4688 case 'width': size = state.chartHeight(); break;
4689 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4690 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4692 default: size = state.chartWidth(); break;
4694 state.element.style.width = size + 'px';
4695 state.element.style.height = size + 'px';
4697 var stroke = Math.floor(size / 22);
4698 if(stroke < 3) stroke = 2;
4700 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4701 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4702 state.easyPieChartLabel = document.createElement('span');
4703 state.easyPieChartLabel.className = 'easyPieChartLabel';
4704 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4705 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4706 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4707 state.element_chart.appendChild(state.easyPieChartLabel);
4709 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4710 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4711 state.easyPieChartTitle = document.createElement('span');
4712 state.easyPieChartTitle.className = 'easyPieChartTitle';
4713 state.easyPieChartTitle.innerHTML = state.title;
4714 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4715 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4716 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4717 state.element_chart.appendChild(state.easyPieChartTitle);
4719 var unitfontsize = Math.round(titlefontsize * 0.9);
4720 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4721 state.easyPieChartUnits = document.createElement('span');
4722 state.easyPieChartUnits.className = 'easyPieChartUnits';
4723 state.easyPieChartUnits.innerHTML = state.units;
4724 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4725 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4726 state.element_chart.appendChild(state.easyPieChartUnits);
4728 chart.easyPieChart({
4729 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4730 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4731 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4732 scaleLength: self.data('easypiechart-scalelength') || 5,
4733 lineCap: self.data('easypiechart-linecap') || 'round',
4734 lineWidth: self.data('easypiechart-linewidth') || stroke,
4735 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4736 size: self.data('easypiechart-size') || size,
4737 rotate: self.data('easypiechart-rotate') || 0,
4738 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4739 easing: self.data('easypiechart-easing') || undefined
4742 // when we just re-create the chart
4743 // do not animate the first update
4745 if(typeof state.easyPieChart_instance !== 'undefined')
4748 state.easyPieChart_instance = chart.data('easyPieChart');
4749 if(animate === false) state.easyPieChart_instance.disableAnimation();
4750 state.easyPieChart_instance.update(pcent);
4751 if(animate === false) state.easyPieChart_instance.enableAnimation();
4755 // ----------------------------------------------------------------------------------------------------------------
4758 NETDATA.gaugeInitialize = function(callback) {
4759 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4761 url: NETDATA.gauge_js,
4766 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4769 NETDATA.chartLibraries.gauge.enabled = false;
4770 NETDATA.error(100, NETDATA.gauge_js);
4772 .always(function() {
4773 if(typeof callback === "function")
4778 NETDATA.chartLibraries.gauge.enabled = false;
4779 if(typeof callback === "function")
4784 NETDATA.gaugeAnimation = function(state, status) {
4787 if(typeof status === 'boolean' && status === false)
4789 else if(typeof status === 'number')
4792 state.gauge_instance.animationSpeed = speed;
4793 state.___gaugeOld__.speed = speed;
4796 NETDATA.gaugeSet = function(state, value, min, max) {
4797 if(typeof value !== 'number') value = 0;
4798 if(typeof min !== 'number') min = 0;
4799 if(typeof max !== 'number') max = 0;
4800 if(value > max) max = value;
4801 if(value < min) min = value;
4810 // gauge.js has an issue if the needle
4811 // is smaller than min or larger than max
4812 // when we set the new values
4813 // the needle will go crazy
4815 // to prevent it, we always feed it
4816 // with a percentage, so that the needle
4817 // is always between min and max
4818 var pcent = (value - min) * 100 / (max - min);
4820 // these should never happen
4821 if(pcent < 0) pcent = 0;
4822 if(pcent > 100) pcent = 100;
4824 state.gauge_instance.set(pcent);
4826 state.___gaugeOld__.value = value;
4827 state.___gaugeOld__.min = min;
4828 state.___gaugeOld__.max = max;
4831 NETDATA.gaugeSetLabels = function(state, value, min, max) {
4832 if(state.___gaugeOld__.valueLabel !== value) {
4833 state.___gaugeOld__.valueLabel = value;
4834 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
4836 if(state.___gaugeOld__.minLabel !== min) {
4837 state.___gaugeOld__.minLabel = min;
4838 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
4840 if(state.___gaugeOld__.maxLabel !== max) {
4841 state.___gaugeOld__.maxLabel = max;
4842 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
4846 NETDATA.gaugeClearSelection = function(state) {
4847 if(typeof state.gaugeEvent !== 'undefined') {
4848 if(state.gaugeEvent.timer !== null)
4849 clearTimeout(state.gaugeEvent.timer);
4851 state.gaugeEvent.timer = null;
4854 if(state.isAutoRefreshed() === true && state.data !== null) {
4855 NETDATA.gaugeChartUpdate(state, state.data);
4858 NETDATA.gaugeAnimation(state, false);
4859 NETDATA.gaugeSet(state, null, null, null);
4860 NETDATA.gaugeSetLabels(state, null, null, null);
4863 NETDATA.gaugeAnimation(state, true);
4867 NETDATA.gaugeSetSelection = function(state, t) {
4868 if(state.timeIsVisible(t) !== true)
4869 return NETDATA.gaugeClearSelection(state);
4871 var slot = state.calculateRowForTime(t);
4872 if(slot < 0 || slot >= state.data.result.length)
4873 return NETDATA.gaugeClearSelection(state);
4875 if(typeof state.gaugeEvent === 'undefined') {
4876 state.gaugeEvent = {
4884 var value = state.data.result[state.data.result.length - 1 - slot];
4885 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
4888 state.gaugeEvent.value = value;
4889 state.gaugeEvent.max = max;
4890 state.gaugeEvent.min = min;
4891 NETDATA.gaugeSetLabels(state, value, min, max);
4893 if(state.gaugeEvent.timer === null) {
4894 NETDATA.gaugeAnimation(state, false);
4896 state.gaugeEvent.timer = setTimeout(function() {
4897 state.gaugeEvent.timer = null;
4898 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
4899 }, NETDATA.options.current.charts_selection_animation_delay);
4905 NETDATA.gaugeChartUpdate = function(state, data) {
4906 var value, min, max;
4908 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
4912 NETDATA.gaugeSetLabels(state, null, null, null);
4915 value = data.result[0];
4917 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
4918 if(value > max) max = value;
4919 NETDATA.gaugeSetLabels(state, value, min, max);
4922 NETDATA.gaugeSet(state, value, min, max);
4926 NETDATA.gaugeChartCreate = function(state, data) {
4927 var self = $(state.element);
4928 var chart = $(state.element_chart);
4930 var value = data.result[0];
4931 var max = self.data('gauge-max-value') || null;
4932 var adjust = self.data('gauge-adjust') || null;
4933 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
4934 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
4935 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
4936 var stopColor = self.data('gauge-stop-color') || void 0;
4937 var generateGradient = self.data('gauge-generate-gradient') || false;
4941 state.gaugeMax = null;
4944 state.gaugeMax = max;
4946 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
4948 // case 'width': width = height * ratio; break;
4950 // default: height = width / ratio; break;
4952 //state.element.style.width = width.toString() + 'px';
4953 //state.element.style.height = height.toString() + 'px';
4958 lines: 12, // The number of lines to draw
4959 angle: 0.15, // The length of each line
4960 lineWidth: 0.44, // 0.44 The line thickness
4962 length: 0.8, // 0.9 The radius of the inner circle
4963 strokeWidth: 0.035, // The rotation offset
4964 color: pointerColor // Fill color
4966 colorStart: startColor, // Colors
4967 colorStop: stopColor, // just experiment with them
4968 strokeColor: strokeColor, // to see which ones work best for you
4970 generateGradient: generateGradient,
4974 if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
4975 options.percentColors = [
4976 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
4977 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
4978 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
4979 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
4980 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
4981 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
4982 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
4983 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
4984 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
4985 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
4986 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
4989 state.gauge_canvas = document.createElement('canvas');
4990 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
4991 state.gauge_canvas.className = 'gaugeChart';
4992 state.gauge_canvas.width = width;
4993 state.gauge_canvas.height = height;
4994 state.element_chart.appendChild(state.gauge_canvas);
4996 var valuefontsize = Math.floor(height / 6);
4997 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
4998 state.gaugeChartLabel = document.createElement('span');
4999 state.gaugeChartLabel.className = 'gaugeChartLabel';
5000 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5001 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5002 state.element_chart.appendChild(state.gaugeChartLabel);
5004 var titlefontsize = Math.round(valuefontsize / 2);
5006 state.gaugeChartTitle = document.createElement('span');
5007 state.gaugeChartTitle.className = 'gaugeChartTitle';
5008 state.gaugeChartTitle.innerHTML = state.title;
5009 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5010 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5011 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5012 state.element_chart.appendChild(state.gaugeChartTitle);
5014 var unitfontsize = Math.round(titlefontsize * 0.9);
5015 state.gaugeChartUnits = document.createElement('span');
5016 state.gaugeChartUnits.className = 'gaugeChartUnits';
5017 state.gaugeChartUnits.innerHTML = state.units;
5018 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5019 state.element_chart.appendChild(state.gaugeChartUnits);
5021 state.gaugeChartMin = document.createElement('span');
5022 state.gaugeChartMin.className = 'gaugeChartMin';
5023 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5024 state.element_chart.appendChild(state.gaugeChartMin);
5026 state.gaugeChartMax = document.createElement('span');
5027 state.gaugeChartMax.className = 'gaugeChartMax';
5028 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5029 state.element_chart.appendChild(state.gaugeChartMax);
5031 // when we just re-create the chart
5032 // do not animate the first update
5034 if(typeof state.gauge_instance !== 'undefined')
5037 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5039 state.___gaugeOld__ = {
5048 // we will always feed a percentage
5049 state.gauge_instance.minValue = 0;
5050 state.gauge_instance.maxValue = 100;
5052 NETDATA.gaugeAnimation(state, animate);
5053 NETDATA.gaugeSet(state, value, 0, max);
5054 NETDATA.gaugeSetLabels(state, value, 0, max);
5055 NETDATA.gaugeAnimation(state, true);
5059 // ----------------------------------------------------------------------------------------------------------------
5060 // Charts Libraries Registration
5062 NETDATA.chartLibraries = {
5064 initialize: NETDATA.dygraphInitialize,
5065 create: NETDATA.dygraphChartCreate,
5066 update: NETDATA.dygraphChartUpdate,
5067 resize: function(state) {
5068 if(typeof state.dygraph_instance.resize === 'function')
5069 state.dygraph_instance.resize();
5071 setSelection: NETDATA.dygraphSetSelection,
5072 clearSelection: NETDATA.dygraphClearSelection,
5073 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5076 format: function(state) { return 'json'; },
5077 options: function(state) { return 'ms|flip'; },
5078 legend: function(state) {
5079 if(this.isSparkline(state) === false)
5080 return 'right-side';
5084 autoresize: function(state) { return true; },
5085 max_updates_to_recreate: function(state) { return 5000; },
5086 track_colors: function(state) { return true; },
5087 pixels_per_point: function(state) {
5088 if(this.isSparkline(state) === false)
5094 isSparkline: function(state) {
5095 if(typeof state.dygraph_sparkline === 'undefined') {
5096 var t = $(state.element).data('dygraph-theme');
5097 if(t === 'sparkline')
5098 state.dygraph_sparkline = true;
5100 state.dygraph_sparkline = false;
5102 return state.dygraph_sparkline;
5106 initialize: NETDATA.sparklineInitialize,
5107 create: NETDATA.sparklineChartCreate,
5108 update: NETDATA.sparklineChartUpdate,
5110 setSelection: undefined, // function(state, t) { return true; },
5111 clearSelection: undefined, // function(state) { return true; },
5112 toolboxPanAndZoom: null,
5115 format: function(state) { return 'array'; },
5116 options: function(state) { return '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.peityInitialize,
5125 create: NETDATA.peityChartCreate,
5126 update: NETDATA.peityChartUpdate,
5128 setSelection: undefined, // function(state, t) { return true; },
5129 clearSelection: undefined, // function(state) { return true; },
5130 toolboxPanAndZoom: null,
5133 format: function(state) { return 'ssvcomma'; },
5134 options: function(state) { return 'null2zero|flip|abs'; },
5135 legend: function(state) { return null; },
5136 autoresize: function(state) { return false; },
5137 max_updates_to_recreate: function(state) { return 5000; },
5138 track_colors: function(state) { return false; },
5139 pixels_per_point: function(state) { return 3; }
5142 initialize: NETDATA.morrisInitialize,
5143 create: NETDATA.morrisChartCreate,
5144 update: NETDATA.morrisChartUpdate,
5146 setSelection: undefined, // function(state, t) { return true; },
5147 clearSelection: undefined, // function(state) { return true; },
5148 toolboxPanAndZoom: null,
5151 format: function(state) { return 'json'; },
5152 options: function(state) { return 'objectrows|ms'; },
5153 legend: function(state) { return null; },
5154 autoresize: function(state) { return false; },
5155 max_updates_to_recreate: function(state) { return 50; },
5156 track_colors: function(state) { return false; },
5157 pixels_per_point: function(state) { return 15; }
5160 initialize: NETDATA.googleInitialize,
5161 create: NETDATA.googleChartCreate,
5162 update: NETDATA.googleChartUpdate,
5164 setSelection: undefined, //function(state, t) { return true; },
5165 clearSelection: undefined, //function(state) { return true; },
5166 toolboxPanAndZoom: null,
5169 format: function(state) { return 'datatable'; },
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 300; },
5174 track_colors: function(state) { return false; },
5175 pixels_per_point: function(state) { return 4; }
5178 initialize: NETDATA.raphaelInitialize,
5179 create: NETDATA.raphaelChartCreate,
5180 update: NETDATA.raphaelChartUpdate,
5182 setSelection: undefined, // function(state, t) { return true; },
5183 clearSelection: undefined, // function(state) { return true; },
5184 toolboxPanAndZoom: null,
5187 format: function(state) { return 'json'; },
5188 options: function(state) { return ''; },
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 3; }
5196 initialize: NETDATA.c3Initialize,
5197 create: NETDATA.c3ChartCreate,
5198 update: NETDATA.c3ChartUpdate,
5200 setSelection: undefined, // function(state, t) { return true; },
5201 clearSelection: undefined, // function(state) { return true; },
5202 toolboxPanAndZoom: null,
5205 format: function(state) { return 'csvjsonarray'; },
5206 options: function(state) { return 'milliseconds'; },
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 15; }
5214 initialize: NETDATA.d3Initialize,
5215 create: NETDATA.d3ChartCreate,
5216 update: NETDATA.d3ChartUpdate,
5218 setSelection: undefined, // function(state, t) { return true; },
5219 clearSelection: undefined, // function(state) { return true; },
5220 toolboxPanAndZoom: null,
5223 format: function(state) { return 'json'; },
5224 options: function(state) { return ''; },
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 false; },
5229 pixels_per_point: function(state) { return 3; }
5232 initialize: NETDATA.easypiechartInitialize,
5233 create: NETDATA.easypiechartChartCreate,
5234 update: NETDATA.easypiechartChartUpdate,
5236 setSelection: NETDATA.easypiechartSetSelection,
5237 clearSelection: NETDATA.easypiechartClearSelection,
5238 toolboxPanAndZoom: null,
5241 format: function(state) { return 'array'; },
5242 options: function(state) { return 'absolute'; },
5243 legend: function(state) { return null; },
5244 autoresize: function(state) { return false; },
5245 max_updates_to_recreate: function(state) { return 5000; },
5246 track_colors: function(state) { return true; },
5247 pixels_per_point: function(state) { return 3; },
5251 initialize: NETDATA.gaugeInitialize,
5252 create: NETDATA.gaugeChartCreate,
5253 update: NETDATA.gaugeChartUpdate,
5255 setSelection: NETDATA.gaugeSetSelection,
5256 clearSelection: NETDATA.gaugeClearSelection,
5257 toolboxPanAndZoom: null,
5260 format: function(state) { return 'array'; },
5261 options: function(state) { return 'absolute'; },
5262 legend: function(state) { return null; },
5263 autoresize: function(state) { return false; },
5264 max_updates_to_recreate: function(state) { return 5000; },
5265 track_colors: function(state) { return true; },
5266 pixels_per_point: function(state) { return 3; },
5271 NETDATA.registerChartLibrary = function(library, url) {
5272 if(NETDATA.options.debug.libraries === true)
5273 console.log("registering chart library: " + library);
5275 NETDATA.chartLibraries[library].url = url;
5276 NETDATA.chartLibraries[library].initialized = true;
5277 NETDATA.chartLibraries[library].enabled = true;
5280 // ----------------------------------------------------------------------------------------------------------------
5283 NETDATA.requiredJs = [
5285 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5286 isAlreadyLoaded: function() {
5287 if(typeof $().emulateTransitionEnd == 'function')
5290 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5298 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5299 isAlreadyLoaded: function() { return false; }
5302 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5303 isAlreadyLoaded: function() { return false; }
5307 NETDATA.requiredCSS = [
5309 url: NETDATA.themes.current.bootstrap_css,
5310 isAlreadyLoaded: function() {
5311 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5318 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5319 isAlreadyLoaded: function() { return false; }
5322 url: NETDATA.themes.current.dashboard_css,
5323 isAlreadyLoaded: function() { return false; }
5326 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5327 isAlreadyLoaded: function() { return false; }
5331 NETDATA.loadRequiredJs = function(index, callback) {
5332 if(index >= NETDATA.requiredJs.length) {
5333 if(typeof callback === 'function')
5338 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5339 NETDATA.loadRequiredJs(++index, callback);
5343 if(NETDATA.options.debug.main_loop === true)
5344 console.log('loading ' + NETDATA.requiredJs[index].url);
5347 url: NETDATA.requiredJs[index].url,
5351 .success(function() {
5352 if(NETDATA.options.debug.main_loop === true)
5353 console.log('loaded ' + NETDATA.requiredJs[index].url);
5355 NETDATA.loadRequiredJs(++index, callback);
5358 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5362 NETDATA.loadRequiredCSS = function(index) {
5363 if(index >= NETDATA.requiredCSS.length)
5366 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5367 NETDATA.loadRequiredCSS(++index);
5371 if(NETDATA.options.debug.main_loop === true)
5372 console.log('loading ' + NETDATA.requiredCSS[index].url);
5374 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5375 NETDATA.loadRequiredCSS(++index);
5378 NETDATA.errorReset();
5379 NETDATA.loadRequiredCSS(0);
5381 NETDATA._loadjQuery(function() {
5382 NETDATA.loadRequiredJs(0, function() {
5383 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
5384 if(NETDATA.options.debug.main_loop === true)
5385 console.log('starting chart refresh thread');
5392 // window.NETDATA = NETDATA;
5393 // })(window, document);