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_count > parent.unselected_count)
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 variables
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 that.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 if(this.debug === true)
1780 this.log(logme + 'flipped parameters, rejecting it.');
1785 if(typeof this.fixed_min_duration === 'undefined')
1786 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1788 var min_duration = this.fixed_min_duration;
1789 var current_duration = Math.round(this.view_before - this.view_after);
1791 // round the numbers
1792 after = Math.round(after);
1793 before = Math.round(before);
1795 // align them to update_every
1796 // stretching them further away
1797 after -= after % this.data_update_every;
1798 before += this.data_update_every - (before % this.data_update_every);
1800 // the final wanted duration
1801 var wanted_duration = before - after;
1803 // to allow panning, accept just a point below our minimum
1804 if((current_duration - this.data_update_every) < min_duration)
1805 min_duration = current_duration - this.data_update_every;
1807 // we do it, but we adjust to minimum size and return false
1808 // when the wanted size is below the current and the minimum
1810 if(wanted_duration < current_duration && wanted_duration < min_duration) {
1811 if(this.debug === true)
1812 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1814 min_duration = this.fixed_min_duration;
1816 var dt = (min_duration - wanted_duration) / 2;
1819 wanted_duration = before - after;
1823 var tolerance = this.data_update_every * 2;
1824 var movement = Math.abs(before - this.view_before);
1826 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
1827 if(this.debug === true)
1828 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);
1832 if(this.current.name === 'auto') {
1833 this.log(logme + 'caller called me with mode: ' + this.current.name);
1834 this.setMode('pan');
1837 if(this.debug === true)
1838 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);
1840 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1841 this.current.force_after_ms = after;
1842 this.current.force_before_ms = before;
1843 NETDATA.globalPanAndZoom.setMaster(this, after, before);
1847 this.legendFormatValue = function(value) {
1848 if(value === null || value === 'undefined') return '-';
1849 if(typeof value !== 'number') return value;
1851 var abs = Math.abs(value);
1852 if(abs >= 1000) return (Math.round(value)).toLocaleString();
1853 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1854 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
1855 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1856 return (Math.round(value * 10000) / 10000).toLocaleString();
1859 this.legendSetLabelValue = function(label, value) {
1860 var series = this.element_legend_childs.series[label];
1861 if(typeof series === 'undefined') return;
1862 if(series.value === null && series.user === null) return;
1864 // if the value has not changed, skip DOM update
1865 //if(series.last === value) return;
1868 if(typeof value === 'number') {
1869 var v = Math.abs(value);
1870 s = r = this.legendFormatValue(value);
1872 if(typeof series.last === 'number') {
1873 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1874 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1875 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1877 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
1882 series.last = value;
1885 if(series.value !== null) series.value.innerHTML = s;
1886 if(series.user !== null) series.user.innerHTML = r;
1889 this.legendSetDate = function(ms) {
1890 if(typeof ms !== 'number') {
1891 this.legendShowUndefined();
1895 var d = new Date(ms);
1897 if(this.element_legend_childs.title_date)
1898 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
1900 if(this.element_legend_childs.title_time)
1901 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
1903 if(this.element_legend_childs.title_units)
1904 this.element_legend_childs.title_units.innerHTML = this.units;
1907 this.legendShowUndefined = function() {
1908 if(this.element_legend_childs.title_date)
1909 this.element_legend_childs.title_date.innerHTML = ' ';
1911 if(this.element_legend_childs.title_time)
1912 this.element_legend_childs.title_time.innerHTML = this.chart.name;
1914 if(this.element_legend_childs.title_units)
1915 this.element_legend_childs.title_units.innerHTML = ' ';
1917 if(this.data && this.element_legend_childs.series !== null) {
1918 var labels = this.data.dimension_names;
1919 var i = labels.length;
1921 var label = labels[i];
1923 if(typeof label === 'undefined') continue;
1924 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1925 this.legendSetLabelValue(label, null);
1930 this.legendShowLatestValues = function() {
1931 if(this.chart === null) return;
1932 if(this.selected) return;
1934 if(this.data === null || this.element_legend_childs.series === null) {
1935 this.legendShowUndefined();
1939 var show_undefined = true;
1940 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
1941 show_undefined = false;
1943 if(show_undefined) {
1944 this.legendShowUndefined();
1948 this.legendSetDate(this.view_before);
1950 var labels = this.data.dimension_names;
1951 var i = labels.length;
1953 var label = labels[i];
1955 if(typeof label === 'undefined') continue;
1956 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
1959 this.legendSetLabelValue(label, null);
1961 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
1965 this.legendReset = function() {
1966 this.legendShowLatestValues();
1969 // this should be called just ONCE per dimension per chart
1970 this._chartDimensionColor = function(label) {
1971 if(this.colors === null) this.chartColors();
1973 if(typeof this.colors_assigned[label] === 'undefined') {
1974 if(this.colors_available.length === 0) {
1975 for(var i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
1976 this.colors_available.push(NETDATA.themes.current.colors[i]);
1979 this.colors_assigned[label] = this.colors_available.shift();
1981 if(this.debug === true)
1982 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
1985 if(this.debug === true)
1986 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
1989 this.colors.push(this.colors_assigned[label]);
1990 return this.colors_assigned[label];
1993 this.chartColors = function() {
1994 if(this.colors !== null) return this.colors;
1996 this.colors = new Array();
1997 this.colors_available = new Array();
2000 var c = $(this.element).data('colors');
2001 // this.log('read colors: ' + c);
2002 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2003 if(typeof c !== 'string') {
2004 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2011 for(i = 0, len = c.length; i < len ; i++) {
2013 this.colors_available.push(c[i]);
2014 // this.log('adding color: ' + c[i]);
2020 // push all the standard colors too
2021 for(i = 0, len = NETDATA.themes.current.colors.length; i < len ; i++)
2022 this.colors_available.push(NETDATA.themes.current.colors[i]);
2027 this.legendUpdateDOM = function() {
2030 // check that the legend DOM is up to date for the downloaded dimensions
2031 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2032 // this.log('the legend does not have any series - requesting legend update');
2035 else if(this.data === null) {
2036 // this.log('the chart does not have any data - requesting legend update');
2039 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2043 var labels = this.data.dimension_names.toString();
2044 if(labels !== this.element_legend_childs.series.labels_key) {
2047 if(this.debug === true)
2048 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2052 if(needed === false) {
2053 // make sure colors available
2056 // do we have to update the current values?
2057 // we do this, only when the visible chart is current
2058 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2059 if(this.debug === true)
2060 this.log('chart is in latest position... updating values on legend...');
2062 //var labels = this.data.dimension_names;
2063 //var i = labels.length;
2065 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2069 if(this.colors === null) {
2070 // this is the first time we update the chart
2071 // let's assign colors to all dimensions
2072 if(this.library.track_colors() === true)
2073 for(var dim in this.chart.dimensions)
2074 this._chartDimensionColor(this.chart.dimensions[dim].name);
2076 // we will re-generate the colors for the chart
2077 // based on the selected dimensions
2080 if(this.debug === true)
2081 this.log('updating Legend DOM');
2083 // mark all dimensions as invalid
2084 this.dimensions_visibility.invalidateAll();
2086 var genLabel = function(state, parent, name, count) {
2087 var color = state._chartDimensionColor(name);
2089 var user_element = null;
2090 var user_id = self.data('show-value-of-' + name + '-at') || null;
2091 if(user_id !== null) {
2092 user_element = document.getElementById(user_id) || null;
2093 if(user_element === null)
2094 state.log('Cannot find element with id: ' + user_id);
2097 state.element_legend_childs.series[name] = {
2098 name: document.createElement('span'),
2099 value: document.createElement('span'),
2104 var label = state.element_legend_childs.series[name];
2106 // create the dimension visibility tracking for this label
2107 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2109 var rgb = NETDATA.colorHex2Rgb(color);
2110 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2111 + state.chart.chart_type
2112 + '" style="background-color: '
2113 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2114 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2116 var text = document.createTextNode(' ' + name);
2117 label.name.appendChild(text);
2120 parent.appendChild(document.createElement('br'));
2122 parent.appendChild(label.name);
2123 parent.appendChild(label.value);
2126 var content = document.createElement('div');
2128 if(this.hasLegend()) {
2129 this.element_legend_childs = {
2131 resize_handler: document.createElement('div'),
2132 toolbox: document.createElement('div'),
2133 toolbox_left: document.createElement('div'),
2134 toolbox_right: document.createElement('div'),
2135 toolbox_reset: document.createElement('div'),
2136 toolbox_zoomin: document.createElement('div'),
2137 toolbox_zoomout: document.createElement('div'),
2138 toolbox_volume: document.createElement('div'),
2139 title_date: document.createElement('span'),
2140 title_time: document.createElement('span'),
2141 title_units: document.createElement('span'),
2142 nano: document.createElement('div'),
2144 paneClass: 'netdata-legend-series-pane',
2145 sliderClass: 'netdata-legend-series-slider',
2146 contentClass: 'netdata-legend-series-content',
2147 enabledClass: '__enabled',
2148 flashedClass: '__flashed',
2149 activeClass: '__active',
2151 alwaysVisible: true,
2157 this.element_legend.innerHTML = '';
2159 if(this.library.toolboxPanAndZoom !== null) {
2161 function get_pan_and_zoom_step(event) {
2163 return NETDATA.options.current.pan_and_zoom_step * NETDATA.options.current.pan_and_zoom_step_multiplier_shift;
2164 else if (event.altKey)
2165 return NETDATA.options.current.pan_and_zoom_step * NETDATA.options.current.pan_and_zoom_step_multiplier_alt;
2166 else if (event.ctrlKey)
2167 return NETDATA.options.current.pan_and_zoom_step * NETDATA.options.current.pan_and_zoom_step_multiplier_control;
2169 return NETDATA.options.current.pan_and_zoom_step;
2172 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2173 this.element.appendChild(this.element_legend_childs.toolbox);
2175 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2176 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2177 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2178 this.element_legend_childs.toolbox_left.onclick = function(e) {
2181 var dt = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2182 var before = that.view_before - dt;
2183 var after = that.view_after - dt;
2184 if(after >= that.netdata_first)
2185 that.library.toolboxPanAndZoom(that, after, before);
2187 if(NETDATA.options.current.show_help === true)
2188 $(this.element_legend_childs.toolbox_left).popover({
2193 placement: 'bottom',
2194 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2196 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>'
2200 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2201 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2202 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2203 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2205 NETDATA.resetAllCharts(that);
2207 if(NETDATA.options.current.show_help === true)
2208 $(this.element_legend_childs.toolbox_reset).popover({
2213 placement: 'bottom',
2214 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2215 title: 'Chart Reset',
2216 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>'
2219 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2220 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2221 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2222 this.element_legend_childs.toolbox_right.onclick = function(e) {
2224 var dt = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2225 var before = that.view_before + dt;
2226 var after = that.view_after + dt;
2227 if(before <= that.netdata_last)
2228 that.library.toolboxPanAndZoom(that, after, before);
2230 if(NETDATA.options.current.show_help === true)
2231 $(this.element_legend_childs.toolbox_right).popover({
2236 placement: 'bottom',
2237 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2239 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>'
2243 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2244 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2245 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2246 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2248 var dt = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2249 var before = that.view_before - dt;
2250 var after = that.view_after + dt;
2251 that.library.toolboxPanAndZoom(that, after, before);
2253 if(NETDATA.options.current.show_help === true)
2254 $(this.element_legend_childs.toolbox_zoomin).popover({
2259 placement: 'bottom',
2260 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2261 title: 'Chart Zoom In',
2262 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>'
2265 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2266 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2267 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2268 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2270 var dt = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2271 var before = that.view_before + dt;
2272 var after = that.view_after - dt;
2274 that.library.toolboxPanAndZoom(that, after, before);
2276 if(NETDATA.options.current.show_help === true)
2277 $(this.element_legend_childs.toolbox_zoomout).popover({
2282 placement: 'bottom',
2283 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2284 title: 'Chart Zoom Out',
2285 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>'
2288 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2289 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2290 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2291 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2292 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2293 //e.preventDefault();
2294 //alert('clicked toolbox_volume on ' + that.id);
2298 this.element_legend_childs.toolbox = null;
2299 this.element_legend_childs.toolbox_left = null;
2300 this.element_legend_childs.toolbox_reset = null;
2301 this.element_legend_childs.toolbox_right = null;
2302 this.element_legend_childs.toolbox_zoomin = null;
2303 this.element_legend_childs.toolbox_zoomout = null;
2304 this.element_legend_childs.toolbox_volume = null;
2307 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2308 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2309 this.element.appendChild(this.element_legend_childs.resize_handler);
2310 if(NETDATA.options.current.show_help === true)
2311 $(this.element_legend_childs.resize_handler).popover({
2316 placement: 'bottom',
2317 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2318 title: 'Chart Resize',
2319 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>'
2323 this.element_legend_childs.resize_handler.onmousedown =
2325 that.resizeHandler(e);
2329 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2330 that.resizeHandler(e);
2333 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2334 this.element_legend.appendChild(this.element_legend_childs.title_date);
2336 this.element_legend.appendChild(document.createElement('br'));
2338 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2339 this.element_legend.appendChild(this.element_legend_childs.title_time);
2341 this.element_legend.appendChild(document.createElement('br'));
2343 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2344 this.element_legend.appendChild(this.element_legend_childs.title_units);
2346 this.element_legend.appendChild(document.createElement('br'));
2348 this.element_legend_childs.nano.className = 'netdata-legend-series';
2349 this.element_legend.appendChild(this.element_legend_childs.nano);
2351 content.className = 'netdata-legend-series-content';
2352 this.element_legend_childs.nano.appendChild(content);
2354 if(NETDATA.options.current.show_help === true)
2355 $(content).popover({
2360 placement: 'bottom',
2361 title: 'Chart Legend',
2362 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2363 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>'
2367 this.element_legend_childs = {
2369 resize_handler: null,
2372 toolbox_right: null,
2373 toolbox_reset: null,
2374 toolbox_zoomin: null,
2375 toolbox_zoomout: null,
2376 toolbox_volume: null,
2387 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2388 if(this.debug === true)
2389 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2391 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2392 genLabel(this, content, this.data.dimension_names[i], i);
2396 var tmp = new Array();
2397 for(var dim in this.chart.dimensions) {
2398 tmp.push(this.chart.dimensions[dim].name);
2399 genLabel(this, content, this.chart.dimensions[dim].name, i);
2401 this.element_legend_childs.series.labels_key = tmp.toString();
2402 if(this.debug === true)
2403 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2406 // create a hidden div to be used for hidding
2407 // the original legend of the chart library
2408 var el = document.createElement('div');
2409 if(this.element_legend !== null)
2410 this.element_legend.appendChild(el);
2411 el.style.display = 'none';
2413 this.element_legend_childs.hidden = document.createElement('div');
2414 el.appendChild(this.element_legend_childs.hidden);
2416 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2417 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2419 this.legendShowLatestValues();
2422 this.hasLegend = function() {
2423 if(typeof this.___hasLegendCache___ !== 'undefined')
2424 return this.___hasLegendCache___;
2427 if(this.library && this.library.legend(this) === 'right-side') {
2428 var legend = $(this.element).data('legend') || 'yes';
2429 if(legend === 'yes') leg = true;
2432 this.___hasLegendCache___ = leg;
2436 this.legendWidth = function() {
2437 return (this.hasLegend())?140:0;
2440 this.legendHeight = function() {
2441 return $(this.element).height();
2444 this.chartWidth = function() {
2445 return $(this.element).width() - this.legendWidth();
2448 this.chartHeight = function() {
2449 return $(this.element).height();
2452 this.chartPixelsPerPoint = function() {
2453 // force an options provided detail
2454 var px = this.pixels_per_point;
2456 if(this.library && px < this.library.pixels_per_point(this))
2457 px = this.library.pixels_per_point(this);
2459 if(px < NETDATA.options.current.pixels_per_point)
2460 px = NETDATA.options.current.pixels_per_point;
2465 this.needsRecreation = function() {
2467 this.chart_created === true
2469 && this.library.autoresize() === false
2470 && this.tm.last_resized < NETDATA.options.last_resized
2474 this.chartURL = function() {
2475 var after, before, points_multiplier = 1;
2476 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2477 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2479 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2480 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2481 this.view_after = after * 1000;
2482 this.view_before = before * 1000;
2484 this.requested_padding = null;
2485 points_multiplier = 1;
2487 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2488 this.tm.pan_and_zoom_seq = 0;
2490 before = Math.round(this.current.force_before_ms / 1000);
2491 after = Math.round(this.current.force_after_ms / 1000);
2492 this.view_after = after * 1000;
2493 this.view_before = before * 1000;
2495 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2496 this.requested_padding = Math.round((before - after) / 2);
2497 after -= this.requested_padding;
2498 before += this.requested_padding;
2499 this.requested_padding *= 1000;
2500 points_multiplier = 2;
2503 this.current.force_before_ms = null;
2504 this.current.force_after_ms = null;
2507 this.tm.pan_and_zoom_seq = 0;
2509 before = this.before;
2511 this.view_after = after * 1000;
2512 this.view_before = before * 1000;
2514 this.requested_padding = null;
2515 points_multiplier = 1;
2518 this.requested_after = after * 1000;
2519 this.requested_before = before * 1000;
2521 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2523 // build the data URL
2524 this.data_url = this.host + this.chart.data_url;
2525 this.data_url += "&format=" + this.library.format();
2526 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2527 this.data_url += "&group=" + this.method;
2528 this.data_url += "&options=" + this.library.options(this);
2529 this.data_url += '|jsonwrap';
2531 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2532 this.data_url += '|nonzero';
2534 if(this.append_options !== null)
2535 this.data_url += '|' + this.append_options.toString();
2538 this.data_url += "&after=" + after.toString();
2541 this.data_url += "&before=" + before.toString();
2544 this.data_url += "&dimensions=" + this.dimensions;
2546 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2547 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2550 this.redrawChart = function() {
2551 if(this.data !== null)
2552 this.updateChartWithData(this.data);
2555 this.updateChartWithData = function(data) {
2556 if(this.debug === true)
2557 this.log('updateChartWithData() called.');
2559 this._updating = false;
2561 // this may force the chart to be re-created
2565 this.updates_counter++;
2566 this.updates_since_last_unhide++;
2567 this.updates_since_last_creation++;
2569 var started = new Date().getTime();
2571 // if the result is JSON, find the latest update-every
2572 this.data_update_every = data.view_update_every * 1000;
2573 this.data_after = data.after * 1000;
2574 this.data_before = data.before * 1000;
2575 this.netdata_first = data.first_entry * 1000;
2576 this.netdata_last = data.last_entry * 1000;
2577 this.data_points = data.points;
2580 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2581 if(this.view_after < this.data_after) {
2582 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2583 this.view_after = this.data_after;
2586 if(this.view_before > this.data_before) {
2587 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2588 this.view_before = this.data_before;
2592 this.view_after = this.data_after;
2593 this.view_before = this.data_before;
2596 if(this.debug === true) {
2597 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2599 if(this.current.force_after_ms)
2600 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2602 this.log('STATUS: forced : unset');
2604 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2605 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2606 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2607 this.log('STATUS: points : ' + (this.data_points).toString());
2610 if(this.data_points === 0) {
2615 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2616 if(this.debug === true)
2617 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2619 this.chart_created = false;
2622 // check and update the legend
2623 this.legendUpdateDOM();
2625 if(this.chart_created === true
2626 && typeof this.library.update === 'function') {
2628 if(this.debug === true)
2629 this.log('updating chart...');
2631 if(callChartLibraryUpdateSafely(data) === false)
2635 if(this.debug === true)
2636 this.log('creating chart...');
2638 if(callChartLibraryCreateSafely(data) === false)
2642 this.legendShowLatestValues();
2643 if(this.selected === true)
2644 NETDATA.globalSelectionSync.stop();
2646 // update the performance counters
2647 var now = new Date().getTime();
2648 this.tm.last_updated = now;
2650 // don't update last_autorefreshed if this chart is
2651 // forced to be updated with global PanAndZoom
2652 if(NETDATA.globalPanAndZoom.isActive())
2653 this.tm.last_autorefreshed = 0;
2655 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes)
2656 this.tm.last_autorefreshed = Math.round(now / this.data_update_every) * this.data_update_every;
2658 this.tm.last_autorefreshed = now;
2661 this.refresh_dt_ms = now - started;
2662 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2664 if(this.refresh_dt_element !== null)
2665 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2668 this.updateChart = function(callback) {
2669 if(this.debug === true)
2670 this.log('updateChart() called.');
2672 if(this._updating === true) {
2673 if(this.debug === true)
2674 this.log('I am already updating...');
2676 if(typeof callback === 'function') callback();
2680 // due to late initialization of charts and libraries
2681 // we need to check this too
2682 if(this.enabled === false) {
2683 if(this.debug === true)
2684 this.log('I am not enabled');
2686 if(typeof callback === 'function') callback();
2690 if(canBeRendered() === false) {
2691 if(typeof callback === 'function') callback();
2695 if(this.chart === null) {
2696 this.getChart(function() { that.updateChart(callback); });
2700 if(this.library.initialized === false) {
2701 if(this.library.enabled === true) {
2702 this.library.initialize(function() { that.updateChart(callback); });
2706 error('chart library "' + this.library_name + '" is not available.');
2707 if(typeof callback === 'function') callback();
2712 this.clearSelection();
2715 if(this.debug === true)
2716 this.log('updating from ' + this.data_url);
2718 this._updating = true;
2720 this.xhr = $.ajax( {
2722 crossDomain: NETDATA.options.crossDomainAjax,
2726 .success(function(data) {
2727 if(that.debug === true)
2728 that.log('data received. updating chart.');
2730 that.updateChartWithData(data);
2733 error('data download failed for url: ' + that.data_url);
2735 .always(function() {
2736 that._updating = false;
2737 if(typeof callback === 'function') callback();
2743 this.isVisible = function(nocache) {
2744 if(typeof nocache === 'undefined')
2747 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2749 // caching - we do not evaluate the charts visibility
2750 // if the page has not been scrolled since the last check
2751 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2752 return this.___isVisible___;
2754 this.tm.last_visible_check = new Date().getTime();
2756 var wh = window.innerHeight;
2757 var x = this.element.getBoundingClientRect();
2761 if(x.width === 0 || x.height === 0) {
2763 this.___isVisible___ = false;
2764 return this.___isVisible___;
2767 if(x.top < 0 && -x.top > x.height) {
2768 // the chart is entirely above
2769 ret = -x.top - x.height;
2771 else if(x.top > wh) {
2772 // the chart is entirely below
2776 if(ret > tolerance) {
2777 // the chart is too far
2780 this.___isVisible___ = false;
2781 return this.___isVisible___;
2784 // the chart is inside or very close
2787 this.___isVisible___ = true;
2788 return this.___isVisible___;
2792 this.isAutoRefreshed = function() {
2793 return (this.current.autorefresh);
2796 this.canBeAutoRefreshed = function() {
2797 var now = new Date().getTime();
2799 if(this.enabled === false) {
2800 if(this.debug === true)
2801 this.log('I am not enabled');
2806 if(this.library === null || this.library.enabled === false) {
2807 error('charting library "' + this.library_name + '" is not available');
2808 if(this.debug === true)
2809 this.log('My chart library ' + this.library_name + ' is not available');
2814 if(this.isVisible() === false) {
2815 if(NETDATA.options.debug.visibility === true || this.debug === true)
2816 this.log('I am not visible');
2821 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2822 if(this.debug === true)
2823 this.log('timed force update detected - allowing this update');
2825 this.current.force_update_at = 0;
2829 if(this.isAutoRefreshed() === true) {
2830 // allow the first update, even if the page is not visible
2831 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2832 if(NETDATA.options.debug.focus === true || this.debug === true)
2833 this.log('canBeAutoRefreshed(): page does not have focus');
2838 if(this.needsRecreation() === true) {
2839 if(this.debug === true)
2840 this.log('canBeAutoRefreshed(): needs re-creation.');
2845 // options valid only for autoRefresh()
2846 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
2847 if(NETDATA.globalPanAndZoom.isActive()) {
2848 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
2849 if(this.debug === true)
2850 this.log('canBeAutoRefreshed(): global panning: I need an update.');
2855 if(this.debug === true)
2856 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
2862 if(this.selected === true) {
2863 if(this.debug === true)
2864 this.log('canBeAutoRefreshed(): I have a selection in place.');
2869 if(this.paused === true) {
2870 if(this.debug === true)
2871 this.log('canBeAutoRefreshed(): I am paused.');
2876 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
2877 if(this.debug === true)
2878 this.log('canBeAutoRefreshed(): It is time to update me.');
2888 this.autoRefresh = function(callback) {
2889 if(this.canBeAutoRefreshed() === true) {
2890 this.updateChart(callback);
2893 if(typeof callback !== 'undefined')
2898 this._defaultsFromDownloadedChart = function(chart) {
2900 this.chart_url = chart.url;
2901 this.data_update_every = chart.update_every * 1000;
2902 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2903 this.tm.last_info_downloaded = new Date().getTime();
2905 if(this.title === null)
2906 this.title = chart.title;
2908 if(this.units === null)
2909 this.units = chart.units;
2912 // fetch the chart description from the netdata server
2913 this.getChart = function(callback) {
2914 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
2916 this._defaultsFromDownloadedChart(this.chart);
2917 if(typeof callback === 'function') callback();
2920 this.chart_url = "/api/v1/chart?chart=" + this.id;
2922 if(this.debug === true)
2923 this.log('downloading ' + this.chart_url);
2926 url: this.host + this.chart_url,
2927 crossDomain: NETDATA.options.crossDomainAjax,
2931 .done(function(chart) {
2932 chart.url = that.chart_url;
2933 that._defaultsFromDownloadedChart(chart);
2934 NETDATA.chartRegistry.add(that.host, that.id, chart);
2937 NETDATA.error(404, that.chart_url);
2938 error('chart not found on url "' + that.chart_url + '"');
2940 .always(function() {
2941 if(typeof callback === 'function') callback();
2946 // ============================================================================================================
2952 NETDATA.resetAllCharts = function(state) {
2953 // first clear the global selection sync
2954 // to make sure no chart is in selected state
2955 state.globalSelectionSyncStop();
2957 // there are 2 possibilities here
2958 // a. state is the global Pan and Zoom master
2959 // b. state is not the global Pan and Zoom master
2961 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
2964 // clear the global Pan and Zoom
2965 // this will also refresh the master
2966 // and unblock any charts currently mirroring the master
2967 NETDATA.globalPanAndZoom.clearMaster();
2969 // if we were not the master, reset our status too
2970 // this is required because most probably the mouse
2971 // is over this chart, blocking it from auto-refreshing
2972 if(master === false && (state.paused === true || state.selected === true))
2976 // get or create a chart state, given a DOM element
2977 NETDATA.chartState = function(element) {
2978 var state = $(element).data('netdata-state-object') || null;
2979 if(state === null) {
2980 state = new chartState(element);
2981 $(element).data('netdata-state-object', state);
2986 // ----------------------------------------------------------------------------------------------------------------
2987 // Library functions
2989 // Load a script without jquery
2990 // This is used to load jquery - after it is loaded, we use jquery
2991 NETDATA._loadjQuery = function(callback) {
2992 if(typeof jQuery === 'undefined') {
2993 if(NETDATA.options.debug.main_loop === true)
2994 console.log('loading ' + NETDATA.jQuery);
2996 var script = document.createElement('script');
2997 script.type = 'text/javascript';
2998 script.async = true;
2999 script.src = NETDATA.jQuery;
3001 // script.onabort = onError;
3002 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3003 if(typeof callback === "function")
3004 script.onload = callback;
3006 var s = document.getElementsByTagName('script')[0];
3007 s.parentNode.insertBefore(script, s);
3009 else if(typeof callback === "function")
3013 NETDATA._loadCSS = function(filename) {
3014 // don't use jQuery here
3015 // styles are loaded before jQuery
3016 // to eliminate showing an unstyled page to the user
3018 var fileref = document.createElement("link");
3019 fileref.setAttribute("rel", "stylesheet");
3020 fileref.setAttribute("type", "text/css");
3021 fileref.setAttribute("href", filename);
3023 if (typeof fileref !== 'undefined')
3024 document.getElementsByTagName("head")[0].appendChild(fileref);
3027 NETDATA.colorHex2Rgb = function(hex) {
3028 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3029 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3030 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3031 return r + r + g + g + b + b;
3034 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3036 r: parseInt(result[1], 16),
3037 g: parseInt(result[2], 16),
3038 b: parseInt(result[3], 16)
3042 NETDATA.colorLuminance = function(hex, lum) {
3043 // validate hex string
3044 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3046 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3050 // convert to decimal and change luminosity
3051 var rgb = "#", c, i;
3052 for (i = 0; i < 3; i++) {
3053 c = parseInt(hex.substr(i*2,2), 16);
3054 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3055 rgb += ("00"+c).substr(c.length);
3061 NETDATA.guid = function() {
3063 return Math.floor((1 + Math.random()) * 0x10000)
3068 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3071 NETDATA.zeropad = function(x) {
3072 if(x > -10 && x < 10) return '0' + x.toString();
3073 else return x.toString();
3076 // user function to signal us the DOM has been
3078 NETDATA.updatedDom = function() {
3079 NETDATA.options.updated_dom = true;
3082 NETDATA.ready = function(callback) {
3083 NETDATA.options.pauseCallback = callback;
3086 NETDATA.pause = function(callback) {
3087 if(NETDATA.options.pause === true)
3090 NETDATA.options.pauseCallback = callback;
3093 NETDATA.unpause = function() {
3094 NETDATA.options.pauseCallback = null;
3095 NETDATA.options.updated_dom = true;
3096 NETDATA.options.pause = false;
3099 // ----------------------------------------------------------------------------------------------------------------
3101 // this is purely sequencial charts refresher
3102 // it is meant to be autonomous
3103 NETDATA.chartRefresherNoParallel = function(index) {
3104 if(NETDATA.options.debug.mail_loop === true)
3105 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3107 if(NETDATA.options.updated_dom === true) {
3108 // the dom has been updated
3109 // get the dom parts again
3110 NETDATA.parseDom(NETDATA.chartRefresher);
3113 if(index >= NETDATA.options.targets.length) {
3114 if(NETDATA.options.debug.main_loop === true)
3115 console.log('waiting to restart main loop...');
3117 NETDATA.options.auto_refresher_fast_weight = 0;
3119 setTimeout(function() {
3120 NETDATA.chartRefresher();
3121 }, NETDATA.options.current.idle_between_loops);
3124 var state = NETDATA.options.targets[index];
3126 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3127 if(NETDATA.options.debug.main_loop === true)
3128 console.log('fast rendering...');
3130 state.autoRefresh(function() {
3131 NETDATA.chartRefresherNoParallel(++index);
3135 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3136 NETDATA.options.auto_refresher_fast_weight = 0;
3138 setTimeout(function() {
3139 state.autoRefresh(function() {
3140 NETDATA.chartRefresherNoParallel(++index);
3142 }, NETDATA.options.current.idle_between_charts);
3147 // this is part of the parallel refresher
3148 // its cause is to refresh sequencially all the charts
3149 // that depend on chart library initialization
3150 // it will call the parallel refresher back
3151 // as soon as it sees a chart that its chart library
3153 NETDATA.chartRefresher_uninitialized = function() {
3154 if(NETDATA.options.updated_dom === true) {
3155 // the dom has been updated
3156 // get the dom parts again
3157 NETDATA.parseDom(NETDATA.chartRefresher);
3161 if(NETDATA.options.sequencial.length === 0)
3162 NETDATA.chartRefresher();
3164 var state = NETDATA.options.sequencial.pop();
3165 if(state.library.initialized === true)
3166 NETDATA.chartRefresher();
3168 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3172 NETDATA.chartRefresherWaitTime = function() {
3173 return NETDATA.options.current.idle_parallel_loops;
3176 // the default refresher
3177 // it will create 2 sets of charts:
3178 // - the ones that can be refreshed in parallel
3179 // - the ones that depend on something else
3180 // the first set will be executed in parallel
3181 // the second will be given to NETDATA.chartRefresher_uninitialized()
3182 NETDATA.chartRefresher = function() {
3183 if(NETDATA.options.pause === true) {
3184 // console.log('auto-refresher is paused');
3185 setTimeout(NETDATA.chartRefresher,
3186 NETDATA.chartRefresherWaitTime());
3190 if(typeof NETDATA.options.pauseCallback === 'function') {
3191 // console.log('auto-refresher is calling pauseCallback');
3192 NETDATA.options.pause = true;
3193 NETDATA.options.pauseCallback();
3194 NETDATA.chartRefresher();
3198 if(NETDATA.options.current.parallel_refresher === false) {
3199 NETDATA.chartRefresherNoParallel(0);
3203 if(NETDATA.options.updated_dom === true) {
3204 // the dom has been updated
3205 // get the dom parts again
3206 NETDATA.parseDom(NETDATA.chartRefresher);
3210 var parallel = new Array();
3211 var targets = NETDATA.options.targets;
3212 var len = targets.length;
3214 if(targets[len].isVisible() === false)
3217 var state = targets[len];
3218 if(state.library.initialized === false) {
3219 if(state.library.enabled === true) {
3220 state.library.initialize(NETDATA.chartRefresher);
3224 state.error('chart library "' + state.library_name + '" is not enabled.');
3228 parallel.unshift(state);
3231 if(parallel.length > 0) {
3232 var parallel_jobs = parallel.length;
3234 // this will execute the jobs in parallel
3235 $(parallel).each(function() {
3236 this.autoRefresh(function() {
3239 if(parallel_jobs === 0) {
3240 setTimeout(NETDATA.chartRefresher,
3241 NETDATA.chartRefresherWaitTime());
3247 setTimeout(NETDATA.chartRefresher,
3248 NETDATA.chartRefresherWaitTime());
3252 NETDATA.parseDom = function(callback) {
3253 NETDATA.options.last_page_scroll = new Date().getTime();
3254 NETDATA.options.updated_dom = false;
3256 var targets = $('div[data-netdata]'); //.filter(':visible');
3258 if(NETDATA.options.debug.main_loop === true)
3259 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3261 NETDATA.options.targets = new Array();
3262 var len = targets.length;
3264 // the initialization will take care of sizing
3265 // and the "loading..." message
3266 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3269 if(typeof callback === 'function') callback();
3272 // this is the main function - where everything starts
3273 NETDATA.start = function() {
3274 // this should be called only once
3276 NETDATA.options.page_is_visible = true;
3278 $(window).blur(function() {
3279 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3280 NETDATA.options.page_is_visible = false;
3281 if(NETDATA.options.debug.focus === true)
3282 console.log('Lost Focus!');
3286 $(window).focus(function() {
3287 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3288 NETDATA.options.page_is_visible = true;
3289 if(NETDATA.options.debug.focus === true)
3290 console.log('Focus restored!');
3294 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3295 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3296 NETDATA.options.page_is_visible = false;
3297 if(NETDATA.options.debug.focus === true)
3298 console.log('Document has no focus!');
3302 // bootstrap tab switching
3303 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3305 // bootstrap modal switching
3306 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3307 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3309 NETDATA.parseDom(NETDATA.chartRefresher);
3312 // ----------------------------------------------------------------------------------------------------------------
3315 NETDATA.peityInitialize = function(callback) {
3316 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3318 url: NETDATA.peity_js,
3323 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3326 NETDATA.chartLibraries.peity.enabled = false;
3327 NETDATA.error(100, NETDATA.peity_js);
3329 .always(function() {
3330 if(typeof callback === "function")
3335 NETDATA.chartLibraries.peity.enabled = false;
3336 if(typeof callback === "function")
3341 NETDATA.peityChartUpdate = function(state, data) {
3342 state.peity_instance.innerHTML = data.result;
3344 if(state.peity_options.stroke !== state.chartColors()[0]) {
3345 state.peity_options.stroke = state.chartColors()[0];
3346 if(state.chart.chart_type === 'line')
3347 state.peity_options.fill = NETDATA.themes.current.background;
3349 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3352 $(state.peity_instance).peity('line', state.peity_options);
3356 NETDATA.peityChartCreate = function(state, data) {
3357 state.peity_instance = document.createElement('div');
3358 state.element_chart.appendChild(state.peity_instance);
3360 var self = $(state.element);
3361 state.peity_options = {
3362 stroke: NETDATA.themes.current.foreground,
3363 strokeWidth: self.data('peity-strokewidth') || 1,
3364 width: state.chartWidth(),
3365 height: state.chartHeight(),
3366 fill: NETDATA.themes.current.foreground
3369 NETDATA.peityChartUpdate(state, data);
3373 // ----------------------------------------------------------------------------------------------------------------
3376 NETDATA.sparklineInitialize = function(callback) {
3377 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3379 url: NETDATA.sparkline_js,
3384 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3387 NETDATA.chartLibraries.sparkline.enabled = false;
3388 NETDATA.error(100, NETDATA.sparkline_js);
3390 .always(function() {
3391 if(typeof callback === "function")
3396 NETDATA.chartLibraries.sparkline.enabled = false;
3397 if(typeof callback === "function")
3402 NETDATA.sparklineChartUpdate = function(state, data) {
3403 state.sparkline_options.width = state.chartWidth();
3404 state.sparkline_options.height = state.chartHeight();
3406 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3410 NETDATA.sparklineChartCreate = function(state, data) {
3411 var self = $(state.element);
3412 var type = self.data('sparkline-type') || 'line';
3413 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3414 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3415 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3416 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3417 var composite = self.data('sparkline-composite') || undefined;
3418 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3419 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3420 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3421 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3422 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3423 var spotColor = self.data('sparkline-spotcolor') || undefined;
3424 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3425 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3426 var spotRadius = self.data('sparkline-spotradius') || undefined;
3427 var valueSpots = self.data('sparkline-valuespots') || undefined;
3428 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3429 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3430 var lineWidth = self.data('sparkline-linewidth') || undefined;
3431 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3432 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3433 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3434 var xvalues = self.data('sparkline-xvalues') || undefined;
3435 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3436 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3437 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3438 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3439 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3440 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3441 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3442 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3443 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3444 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3445 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3446 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3447 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3448 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3449 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3450 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3451 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3452 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3453 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3454 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3455 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3456 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3458 state.sparkline_options = {
3460 lineColor: lineColor,
3461 fillColor: fillColor,
3462 chartRangeMin: chartRangeMin,
3463 chartRangeMax: chartRangeMax,
3464 composite: composite,
3465 enableTagOptions: enableTagOptions,
3466 tagOptionPrefix: tagOptionPrefix,
3467 tagValuesAttribute: tagValuesAttribute,
3468 disableHiddenCheck: disableHiddenCheck,
3469 defaultPixelsPerValue: defaultPixelsPerValue,
3470 spotColor: spotColor,
3471 minSpotColor: minSpotColor,
3472 maxSpotColor: maxSpotColor,
3473 spotRadius: spotRadius,
3474 valueSpots: valueSpots,
3475 highlightSpotColor: highlightSpotColor,
3476 highlightLineColor: highlightLineColor,
3477 lineWidth: lineWidth,
3478 normalRangeMin: normalRangeMin,
3479 normalRangeMax: normalRangeMax,
3480 drawNormalOnTop: drawNormalOnTop,
3482 chartRangeClip: chartRangeClip,
3483 chartRangeMinX: chartRangeMinX,
3484 chartRangeMaxX: chartRangeMaxX,
3485 disableInteraction: disableInteraction,
3486 disableTooltips: disableTooltips,
3487 disableHighlight: disableHighlight,
3488 highlightLighten: highlightLighten,
3489 highlightColor: highlightColor,
3490 tooltipContainer: tooltipContainer,
3491 tooltipClassname: tooltipClassname,
3492 tooltipChartTitle: state.title,
3493 tooltipFormat: tooltipFormat,
3494 tooltipPrefix: tooltipPrefix,
3495 tooltipSuffix: tooltipSuffix,
3496 tooltipSkipNull: tooltipSkipNull,
3497 tooltipValueLookups: tooltipValueLookups,
3498 tooltipFormatFieldlist: tooltipFormatFieldlist,
3499 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3500 numberFormatter: numberFormatter,
3501 numberDigitGroupSep: numberDigitGroupSep,
3502 numberDecimalMark: numberDecimalMark,
3503 numberDigitGroupCount: numberDigitGroupCount,
3504 animatedZooms: animatedZooms,
3505 width: state.chartWidth(),
3506 height: state.chartHeight()
3509 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3513 // ----------------------------------------------------------------------------------------------------------------
3520 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3521 if(after < state.netdata_first)
3522 after = state.netdata_first;
3524 if(before > state.netdata_last)
3525 before = state.netdata_last;
3527 state.setMode('zoom');
3528 state.globalSelectionSyncStop();
3529 state.globalSelectionSyncDelay();
3530 state.dygraph_user_action = true;
3531 state.dygraph_force_zoom = true;
3532 state.updateChartPanOrZoom(after, before);
3533 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3536 NETDATA.dygraphSetSelection = function(state, t) {
3537 if(typeof state.dygraph_instance !== 'undefined') {
3538 var r = state.calculateRowForTime(t);
3540 state.dygraph_instance.setSelection(r);
3542 state.dygraph_instance.clearSelection();
3543 state.legendShowUndefined();
3550 NETDATA.dygraphClearSelection = function(state, t) {
3551 if(typeof state.dygraph_instance !== 'undefined') {
3552 state.dygraph_instance.clearSelection();
3557 NETDATA.dygraphSmoothInitialize = function(callback) {
3559 url: NETDATA.dygraph_smooth_js,
3564 NETDATA.dygraph.smooth = true;
3565 smoothPlotter.smoothing = 0.3;
3568 NETDATA.dygraph.smooth = false;
3570 .always(function() {
3571 if(typeof callback === "function")
3576 NETDATA.dygraphInitialize = function(callback) {
3577 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3579 url: NETDATA.dygraph_js,
3584 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3587 NETDATA.chartLibraries.dygraph.enabled = false;
3588 NETDATA.error(100, NETDATA.dygraph_js);
3590 .always(function() {
3591 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3592 NETDATA.dygraphSmoothInitialize(callback);
3593 else if(typeof callback === "function")
3598 NETDATA.chartLibraries.dygraph.enabled = false;
3599 if(typeof callback === "function")
3604 NETDATA.dygraphChartUpdate = function(state, data) {
3605 var dygraph = state.dygraph_instance;
3607 if(typeof dygraph === 'undefined')
3608 return NETDATA.dygraphChartCreate(state, data);
3610 // when the chart is not visible, and hidden
3611 // if there is a window resize, dygraph detects
3612 // its element size as 0x0.
3613 // this will make it re-appear properly
3615 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3619 file: data.result.data,
3620 colors: state.chartColors(),
3621 labels: data.result.labels,
3622 labelsDivWidth: state.chartWidth() - 70,
3623 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3626 if(state.dygraph_force_zoom === true) {
3627 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3628 state.log('dygraphChartUpdate() forced zoom update');
3630 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3631 options.valueRange = null;
3632 options.isZoomedIgnoreProgrammaticZoom = true;
3633 state.dygraph_force_zoom = false;
3635 else if(state.current.name !== 'auto') {
3636 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3637 state.log('dygraphChartUpdate() loose update');
3640 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3641 state.log('dygraphChartUpdate() strict update');
3643 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3644 options.valueRange = null;
3645 options.isZoomedIgnoreProgrammaticZoom = true;
3648 if(state.dygraph_smooth_eligible === true) {
3649 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3650 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3651 NETDATA.dygraphChartCreate(state, data);
3656 dygraph.updateOptions(options);
3658 state.dygraph_last_rendered = new Date().getTime();
3662 NETDATA.dygraphChartCreate = function(state, data) {
3663 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3664 state.log('dygraphChartCreate()');
3666 var self = $(state.element);
3668 var chart_type = state.chart.chart_type;
3669 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3670 chart_type = self.data('dygraph-type') || chart_type;
3672 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3673 smooth = self.data('dygraph-smooth') || smooth;
3675 if(NETDATA.dygraph.smooth === false)
3678 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3679 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3681 state.dygraph_options = {
3682 colors: self.data('dygraph-colors') || state.chartColors(),
3684 // leave a few pixels empty on the right of the chart
3685 rightGap: self.data('dygraph-rightgap') || 5,
3686 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3687 showRoller: self.data('dygraph-showroller') || false,
3689 title: self.data('dygraph-title') || state.title,
3690 titleHeight: self.data('dygraph-titleheight') || 19,
3692 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3693 labels: data.result.labels,
3694 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3695 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3696 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3697 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3698 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3701 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3702 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3704 ylabel: state.units,
3705 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3707 // the function to plot the chart
3710 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3711 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3712 strokePattern: self.data('dygraph-strokepattern') || undefined,
3714 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3715 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3716 drawPoints: self.data('dygraph-drawpoints') || false,
3718 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3719 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3721 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3722 pointSize: self.data('dygraph-pointsize') || 1,
3724 // enabling this makes the chart with little square lines
3725 stepPlot: self.data('dygraph-stepplot') || false,
3727 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3728 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3729 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3731 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3732 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3733 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3734 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3736 drawAxis: self.data('dygraph-drawaxis') || true,
3737 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3738 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3739 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3741 drawGrid: self.data('dygraph-drawgrid') || true,
3742 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3743 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3744 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3745 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3746 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3748 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3749 sigFigs: self.data('dygraph-sigfigs') || null,
3750 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3751 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3753 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3754 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3755 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3757 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3758 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3762 ticker: Dygraph.dateTicker,
3763 axisLabelFormatter: function (d, gran) {
3764 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3766 valueFormatter: function (ms) {
3767 var d = new Date(ms);
3768 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3769 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3774 valueFormatter: function (x) {
3775 // we format legends with the state object
3776 // no need to do anything here
3777 // return (Math.round(x*100) / 100).toLocaleString();
3778 // return state.legendFormatValue(x);
3783 legendFormatter: function(data) {
3784 var elements = state.element_legend_childs;
3786 // if the hidden div is not there
3787 // we are not managing the legend
3788 if(elements.hidden === null) return;
3790 if (typeof data.x !== 'undefined') {
3791 state.legendSetDate(data.x);
3792 var i = data.series.length;
3794 var series = data.series[i];
3795 if(!series.isVisible) continue;
3796 state.legendSetLabelValue(series.label, series.y);
3802 drawCallback: function(dygraph, is_initial) {
3803 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3804 state.dygraph_user_action = false;
3806 var x_range = dygraph.xAxisRange();
3807 var after = Math.round(x_range[0]);
3808 var before = Math.round(x_range[1]);
3810 if(NETDATA.options.debug.dygraph === true)
3811 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3813 if(before <= state.netdata_last && after >= state.netdata_first)
3814 state.updateChartPanOrZoom(after, before);
3817 zoomCallback: function(minDate, maxDate, yRanges) {
3818 if(NETDATA.options.debug.dygraph === true)
3819 state.log('dygraphZoomCallback()');
3821 state.globalSelectionSyncStop();
3822 state.globalSelectionSyncDelay();
3823 state.setMode('zoom');
3825 // refresh it to the greatest possible zoom level
3826 state.dygraph_user_action = true;
3827 state.dygraph_force_zoom = true;
3828 state.updateChartPanOrZoom(minDate, maxDate);
3830 highlightCallback: function(event, x, points, row, seriesName) {
3831 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3832 state.log('dygraphHighlightCallback()');
3836 // there is a bug in dygraph when the chart is zoomed enough
3837 // the time it thinks is selected is wrong
3838 // here we calculate the time t based on the row number selected
3840 var t = state.data_after + row * state.data_update_every;
3841 // 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);
3843 state.globalSelectionSync(x);
3845 // fix legend zIndex using the internal structures of dygraph legend module
3846 // this works, but it is a hack!
3847 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
3849 unhighlightCallback: function(event) {
3850 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3851 state.log('dygraphUnhighlightCallback()');
3853 state.unpauseChart();
3854 state.globalSelectionSyncStop();
3856 interactionModel : {
3857 mousedown: function(event, dygraph, context) {
3858 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3859 state.log('interactionModel.mousedown()');
3861 state.dygraph_user_action = true;
3862 state.globalSelectionSyncStop();
3864 if(NETDATA.options.debug.dygraph === true)
3865 state.log('dygraphMouseDown()');
3867 // Right-click should not initiate a zoom.
3868 if(event.button && event.button === 2) return;
3870 context.initializeMouseDown(event, dygraph, context);
3872 if(event.button && event.button === 1) {
3873 if (event.altKey || event.shiftKey) {
3874 state.setMode('pan');
3875 state.globalSelectionSyncDelay();
3876 Dygraph.startPan(event, dygraph, context);
3879 state.setMode('zoom');
3880 state.globalSelectionSyncDelay();
3881 Dygraph.startZoom(event, dygraph, context);
3885 if (event.altKey || event.shiftKey) {
3886 state.setMode('zoom');
3887 state.globalSelectionSyncDelay();
3888 Dygraph.startZoom(event, dygraph, context);
3891 state.setMode('pan');
3892 state.globalSelectionSyncDelay();
3893 Dygraph.startPan(event, dygraph, context);
3897 mousemove: function(event, dygraph, context) {
3898 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3899 state.log('interactionModel.mousemove()');
3901 if(context.isPanning) {
3902 state.dygraph_user_action = true;
3903 state.globalSelectionSyncStop();
3904 state.globalSelectionSyncDelay();
3905 state.setMode('pan');
3906 Dygraph.movePan(event, dygraph, context);
3908 else if(context.isZooming) {
3909 state.dygraph_user_action = true;
3910 state.globalSelectionSyncStop();
3911 state.globalSelectionSyncDelay();
3912 state.setMode('zoom');
3913 Dygraph.moveZoom(event, dygraph, context);
3916 mouseup: function(event, dygraph, context) {
3917 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3918 state.log('interactionModel.mouseup()');
3920 if (context.isPanning) {
3921 state.dygraph_user_action = true;
3922 state.globalSelectionSyncDelay();
3923 Dygraph.endPan(event, dygraph, context);
3925 else if (context.isZooming) {
3926 state.dygraph_user_action = true;
3927 state.globalSelectionSyncDelay();
3928 Dygraph.endZoom(event, dygraph, context);
3931 click: function(event, dygraph, context) {
3932 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3933 state.log('interactionModel.click()');
3935 event.preventDefault();
3937 dblclick: function(event, dygraph, context) {
3938 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3939 state.log('interactionModel.dblclick()');
3940 NETDATA.resetAllCharts(state);
3942 mousewheel: function(event, dygraph, context) {
3943 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3944 state.log('interactionModel.mousewheel()');
3946 // Take the offset of a mouse event on the dygraph canvas and
3947 // convert it to a pair of percentages from the bottom left.
3948 // (Not top left, bottom is where the lower value is.)
3949 function offsetToPercentage(g, offsetX, offsetY) {
3950 // This is calculating the pixel offset of the leftmost date.
3951 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
3952 var yar0 = g.yAxisRange(0);
3954 // This is calculating the pixel of the higest value. (Top pixel)
3955 var yOffset = g.toDomCoords(null, yar0[1])[1];
3957 // x y w and h are relative to the corner of the drawing area,
3958 // so that the upper corner of the drawing area is (0, 0).
3959 var x = offsetX - xOffset;
3960 var y = offsetY - yOffset;
3962 // This is computing the rightmost pixel, effectively defining the
3964 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
3966 // This is computing the lowest pixel, effectively defining the height.
3967 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
3969 // Percentage from the left.
3970 var xPct = w === 0 ? 0 : (x / w);
3971 // Percentage from the top.
3972 var yPct = h === 0 ? 0 : (y / h);
3974 // The (1-) part below changes it from "% distance down from the top"
3975 // to "% distance up from the bottom".
3976 return [xPct, (1-yPct)];
3979 // Adjusts [x, y] toward each other by zoomInPercentage%
3980 // Split it so the left/bottom axis gets xBias/yBias of that change and
3981 // tight/top gets (1-xBias)/(1-yBias) of that change.
3983 // If a bias is missing it splits it down the middle.
3984 function zoomRange(g, zoomInPercentage, xBias, yBias) {
3985 xBias = xBias || 0.5;
3986 yBias = yBias || 0.5;
3988 function adjustAxis(axis, zoomInPercentage, bias) {
3989 var delta = axis[1] - axis[0];
3990 var increment = delta * zoomInPercentage;
3991 var foo = [increment * bias, increment * (1-bias)];
3993 return [ axis[0] + foo[0], axis[1] - foo[1] ];
3996 var yAxes = g.yAxisRanges();
3998 for (var i = 0; i < yAxes.length; i++) {
3999 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4002 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4005 if(event.altKey || event.shiftKey) {
4006 state.dygraph_user_action = true;
4008 state.globalSelectionSyncStop();
4009 state.globalSelectionSyncDelay();
4011 // http://dygraphs.com/gallery/interaction-api.js
4012 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4013 var percentage = normal / 50;
4015 if (!(event.offsetX && event.offsetY)){
4016 event.offsetX = event.layerX - event.target.offsetLeft;
4017 event.offsetY = event.layerY - event.target.offsetTop;
4020 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4021 var xPct = percentages[0];
4022 var yPct = percentages[1];
4024 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4026 var after = new_x_range[0];
4027 var before = new_x_range[1];
4029 var first = state.netdata_first + state.data_update_every;
4030 var last = state.netdata_last + state.data_update_every;
4033 after -= (before - last);
4040 state.setMode('zoom');
4041 if(state.updateChartPanOrZoom(after, before) === true)
4042 dygraph.updateOptions({ dateWindow: [ after, before ] });
4044 event.preventDefault();
4047 touchstart: function(event, dygraph, context) {
4048 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4049 state.log('interactionModel.touchstart()');
4051 state.dygraph_user_action = true;
4052 state.setMode('zoom');
4055 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4057 // we overwrite the touch directions at the end, to overwrite
4058 // the internal default of dygraphs
4059 context.touchDirections = { x: true, y: false };
4061 state.dygraph_last_touch_start = new Date().getTime();
4062 state.dygraph_last_touch_move = 0;
4064 if(typeof event.touches[0].pageX === 'number')
4065 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4067 state.dygraph_last_touch_page_x = 0;
4069 touchmove: function(event, dygraph, context) {
4070 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4071 state.log('interactionModel.touchmove()');
4073 state.dygraph_user_action = true;
4074 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4076 state.dygraph_last_touch_move = new Date().getTime();
4078 touchend: function(event, dygraph, context) {
4079 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4080 state.log('interactionModel.touchend()');
4082 state.dygraph_user_action = true;
4083 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4085 // if it didn't move, it is a selection
4086 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4087 // internal api of dygraphs
4088 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4089 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4090 if(NETDATA.dygraphSetSelection(state, t) === true)
4091 state.globalSelectionSync(t);
4094 // if it was double tap within double click time, reset the charts
4095 var now = new Date().getTime();
4096 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4097 if(state.dygraph_last_touch_move === 0) {
4098 var dt = now - state.dygraph_last_touch_end;
4099 if(dt <= NETDATA.options.current.double_click_speed)
4100 NETDATA.resetAllCharts(state);
4104 // remember the timestamp of the last touch end
4105 state.dygraph_last_touch_end = now;
4110 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4111 state.dygraph_options.drawGrid = false;
4112 state.dygraph_options.drawAxis = false;
4113 state.dygraph_options.title = undefined;
4114 state.dygraph_options.units = undefined;
4115 state.dygraph_options.ylabel = undefined;
4116 state.dygraph_options.yLabelWidth = 0;
4117 state.dygraph_options.labelsDivWidth = 120;
4118 state.dygraph_options.labelsDivStyles.width = '120px';
4119 state.dygraph_options.labelsSeparateLines = true;
4120 state.dygraph_options.rightGap = 0;
4123 if(smooth === true) {
4124 state.dygraph_smooth_eligible = true;
4126 if(NETDATA.options.current.smooth_plot === true)
4127 state.dygraph_options.plotter = smoothPlotter;
4129 else state.dygraph_smooth_eligible = false;
4131 state.dygraph_instance = new Dygraph(state.element_chart,
4132 data.result.data, state.dygraph_options);
4134 state.dygraph_force_zoom = false;
4135 state.dygraph_user_action = false;
4136 state.dygraph_last_rendered = new Date().getTime();
4140 // ----------------------------------------------------------------------------------------------------------------
4143 NETDATA.morrisInitialize = function(callback) {
4144 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4146 // morris requires raphael
4147 if(!NETDATA.chartLibraries.raphael.initialized) {
4148 if(NETDATA.chartLibraries.raphael.enabled) {
4149 NETDATA.raphaelInitialize(function() {
4150 NETDATA.morrisInitialize(callback);
4154 NETDATA.chartLibraries.morris.enabled = false;
4155 if(typeof callback === "function")
4160 NETDATA._loadCSS(NETDATA.morris_css);
4163 url: NETDATA.morris_js,
4168 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4171 NETDATA.chartLibraries.morris.enabled = false;
4172 NETDATA.error(100, NETDATA.morris_js);
4174 .always(function() {
4175 if(typeof callback === "function")
4181 NETDATA.chartLibraries.morris.enabled = false;
4182 if(typeof callback === "function")
4187 NETDATA.morrisChartUpdate = function(state, data) {
4188 state.morris_instance.setData(data.result.data);
4192 NETDATA.morrisChartCreate = function(state, data) {
4194 state.morris_options = {
4195 element: state.element_chart.id,
4196 data: data.result.data,
4198 ykeys: data.dimension_names,
4199 labels: data.dimension_names,
4205 continuousLine: false,
4206 behaveLikeLine: false
4209 if(state.chart.chart_type === 'line')
4210 state.morris_instance = new Morris.Line(state.morris_options);
4212 else if(state.chart.chart_type === 'area') {
4213 state.morris_options.behaveLikeLine = true;
4214 state.morris_instance = new Morris.Area(state.morris_options);
4217 state.morris_instance = new Morris.Area(state.morris_options);
4222 // ----------------------------------------------------------------------------------------------------------------
4225 NETDATA.raphaelInitialize = function(callback) {
4226 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4228 url: NETDATA.raphael_js,
4233 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4236 NETDATA.chartLibraries.raphael.enabled = false;
4237 NETDATA.error(100, NETDATA.raphael_js);
4239 .always(function() {
4240 if(typeof callback === "function")
4245 NETDATA.chartLibraries.raphael.enabled = false;
4246 if(typeof callback === "function")
4251 NETDATA.raphaelChartUpdate = function(state, data) {
4252 $(state.element_chart).raphael(data.result, {
4253 width: state.chartWidth(),
4254 height: state.chartHeight()
4260 NETDATA.raphaelChartCreate = function(state, data) {
4261 $(state.element_chart).raphael(data.result, {
4262 width: state.chartWidth(),
4263 height: state.chartHeight()
4269 // ----------------------------------------------------------------------------------------------------------------
4272 NETDATA.c3Initialize = function(callback) {
4273 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4276 if(!NETDATA.chartLibraries.d3.initialized) {
4277 if(NETDATA.chartLibraries.d3.enabled) {
4278 NETDATA.d3Initialize(function() {
4279 NETDATA.c3Initialize(callback);
4283 NETDATA.chartLibraries.c3.enabled = false;
4284 if(typeof callback === "function")
4289 NETDATA._loadCSS(NETDATA.c3_css);
4297 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4300 NETDATA.chartLibraries.c3.enabled = false;
4301 NETDATA.error(100, NETDATA.c3_js);
4303 .always(function() {
4304 if(typeof callback === "function")
4310 NETDATA.chartLibraries.c3.enabled = false;
4311 if(typeof callback === "function")
4316 NETDATA.c3ChartUpdate = function(state, data) {
4317 state.c3_instance.destroy();
4318 return NETDATA.c3ChartCreate(state, data);
4320 //state.c3_instance.load({
4321 // rows: data.result,
4328 NETDATA.c3ChartCreate = function(state, data) {
4330 state.element_chart.id = 'c3-' + state.uuid;
4331 // console.log('id = ' + state.element_chart.id);
4333 state.c3_instance = c3.generate({
4334 bindto: '#' + state.element_chart.id,
4336 width: state.chartWidth(),
4337 height: state.chartHeight()
4340 pattern: state.chartColors()
4345 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4351 format: function(x) {
4352 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4379 // console.log(state.c3_instance);
4384 // ----------------------------------------------------------------------------------------------------------------
4387 NETDATA.d3Initialize = function(callback) {
4388 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4395 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4398 NETDATA.chartLibraries.d3.enabled = false;
4399 NETDATA.error(100, NETDATA.d3_js);
4401 .always(function() {
4402 if(typeof callback === "function")
4407 NETDATA.chartLibraries.d3.enabled = false;
4408 if(typeof callback === "function")
4413 NETDATA.d3ChartUpdate = function(state, data) {
4417 NETDATA.d3ChartCreate = function(state, data) {
4421 // ----------------------------------------------------------------------------------------------------------------
4424 NETDATA.googleInitialize = function(callback) {
4425 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4427 url: NETDATA.google_js,
4432 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4433 google.load('visualization', '1.1', {
4434 'packages': ['corechart', 'controls'],
4435 'callback': callback
4439 NETDATA.chartLibraries.google.enabled = false;
4440 NETDATA.error(100, NETDATA.google_js);
4441 if(typeof callback === "function")
4446 NETDATA.chartLibraries.google.enabled = false;
4447 if(typeof callback === "function")
4452 NETDATA.googleChartUpdate = function(state, data) {
4453 var datatable = new google.visualization.DataTable(data.result);
4454 state.google_instance.draw(datatable, state.google_options);
4458 NETDATA.googleChartCreate = function(state, data) {
4459 var datatable = new google.visualization.DataTable(data.result);
4461 state.google_options = {
4462 colors: state.chartColors(),
4464 // do not set width, height - the chart resizes itself
4465 //width: state.chartWidth(),
4466 //height: state.chartHeight(),
4471 // title: "Time of Day",
4472 // format:'HH:mm:ss',
4473 viewWindowMode: 'maximized',
4485 viewWindowMode: 'pretty',
4500 focusTarget: 'category',
4507 titlePosition: 'out',
4518 curveType: 'function',
4523 switch(state.chart.chart_type) {
4525 state.google_options.vAxis.viewWindowMode = 'maximized';
4526 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4527 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4531 state.google_options.isStacked = true;
4532 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4533 state.google_options.vAxis.viewWindowMode = 'maximized';
4534 state.google_options.vAxis.minValue = null;
4535 state.google_options.vAxis.maxValue = null;
4536 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4541 state.google_options.lineWidth = 2;
4542 state.google_instance = new google.visualization.LineChart(state.element_chart);
4546 state.google_instance.draw(datatable, state.google_options);
4550 // ----------------------------------------------------------------------------------------------------------------
4552 NETDATA.percentFromValueMax = function(value, max) {
4553 if(value === null) value = 0;
4554 if(max < value) max = value;
4558 pcent = Math.round(value * 100 / max);
4559 if(pcent === 0 && value > 0) pcent = 1;
4565 // ----------------------------------------------------------------------------------------------------------------
4568 NETDATA.easypiechartInitialize = function(callback) {
4569 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4571 url: NETDATA.easypiechart_js,
4576 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4579 NETDATA.chartLibraries.easypiechart.enabled = false;
4580 NETDATA.error(100, NETDATA.easypiechart_js);
4582 .always(function() {
4583 if(typeof callback === "function")
4588 NETDATA.chartLibraries.easypiechart.enabled = false;
4589 if(typeof callback === "function")
4594 NETDATA.easypiechartClearSelection = function(state) {
4595 if(typeof state.easyPieChartEvent !== 'undefined') {
4596 if(state.easyPieChartEvent.timer !== null)
4597 clearTimeout(state.easyPieChartEvent.timer);
4599 state.easyPieChartEvent.timer = null;
4602 if(state.isAutoRefreshed() === true && state.data !== null) {
4603 NETDATA.easypiechartChartUpdate(state, state.data);
4606 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4607 state.easyPieChart_instance.update(0);
4609 state.easyPieChart_instance.enableAnimation();
4614 NETDATA.easypiechartSetSelection = function(state, t) {
4615 if(state.timeIsVisible(t) !== true)
4616 return NETDATA.easypiechartClearSelection(state);
4618 var slot = state.calculateRowForTime(t);
4619 if(slot < 0 || slot >= state.data.result.length)
4620 return NETDATA.easypiechartClearSelection(state);
4622 if(typeof state.easyPieChartEvent === 'undefined') {
4623 state.easyPieChartEvent = {
4630 var value = state.data.result[state.data.result.length - 1 - slot];
4631 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4632 var pcent = NETDATA.percentFromValueMax(value, max);
4634 state.easyPieChartEvent.value = value;
4635 state.easyPieChartEvent.pcent = pcent;
4636 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4638 if(state.easyPieChartEvent.timer === null) {
4639 state.easyPieChart_instance.disableAnimation();
4641 state.easyPieChartEvent.timer = setTimeout(function() {
4642 state.easyPieChartEvent.timer = null;
4643 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4644 }, NETDATA.options.current.charts_selection_animation_delay);
4650 NETDATA.easypiechartChartUpdate = function(state, data) {
4651 var value, max, pcent;
4653 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
4659 value = data.result[0];
4660 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4661 pcent = NETDATA.percentFromValueMax(value, max);
4664 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4665 state.easyPieChart_instance.update(pcent);
4669 NETDATA.easypiechartChartCreate = function(state, data) {
4670 var self = $(state.element);
4671 var chart = $(state.element_chart);
4673 var value = data.result[0];
4674 var max = self.data('easypiechart-max-value') || null;
4675 var adjust = self.data('easypiechart-adjust') || null;
4679 state.easyPieChartMax = null;
4682 state.easyPieChartMax = max;
4684 var pcent = NETDATA.percentFromValueMax(value, max);
4686 chart.data('data-percent', pcent);
4690 case 'width': size = state.chartHeight(); break;
4691 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4692 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4694 default: size = state.chartWidth(); break;
4696 state.element.style.width = size + 'px';
4697 state.element.style.height = size + 'px';
4699 var stroke = Math.floor(size / 22);
4700 if(stroke < 3) stroke = 2;
4702 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4703 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4704 state.easyPieChartLabel = document.createElement('span');
4705 state.easyPieChartLabel.className = 'easyPieChartLabel';
4706 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4707 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4708 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4709 state.element_chart.appendChild(state.easyPieChartLabel);
4711 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4712 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4713 state.easyPieChartTitle = document.createElement('span');
4714 state.easyPieChartTitle.className = 'easyPieChartTitle';
4715 state.easyPieChartTitle.innerHTML = state.title;
4716 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4717 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4718 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4719 state.element_chart.appendChild(state.easyPieChartTitle);
4721 var unitfontsize = Math.round(titlefontsize * 0.9);
4722 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4723 state.easyPieChartUnits = document.createElement('span');
4724 state.easyPieChartUnits.className = 'easyPieChartUnits';
4725 state.easyPieChartUnits.innerHTML = state.units;
4726 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4727 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4728 state.element_chart.appendChild(state.easyPieChartUnits);
4730 chart.easyPieChart({
4731 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4732 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4733 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4734 scaleLength: self.data('easypiechart-scalelength') || 5,
4735 lineCap: self.data('easypiechart-linecap') || 'round',
4736 lineWidth: self.data('easypiechart-linewidth') || stroke,
4737 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4738 size: self.data('easypiechart-size') || size,
4739 rotate: self.data('easypiechart-rotate') || 0,
4740 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4741 easing: self.data('easypiechart-easing') || undefined
4744 // when we just re-create the chart
4745 // do not animate the first update
4747 if(typeof state.easyPieChart_instance !== 'undefined')
4750 state.easyPieChart_instance = chart.data('easyPieChart');
4751 if(animate === false) state.easyPieChart_instance.disableAnimation();
4752 state.easyPieChart_instance.update(pcent);
4753 if(animate === false) state.easyPieChart_instance.enableAnimation();
4757 // ----------------------------------------------------------------------------------------------------------------
4760 NETDATA.gaugeInitialize = function(callback) {
4761 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4763 url: NETDATA.gauge_js,
4768 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4771 NETDATA.chartLibraries.gauge.enabled = false;
4772 NETDATA.error(100, NETDATA.gauge_js);
4774 .always(function() {
4775 if(typeof callback === "function")
4780 NETDATA.chartLibraries.gauge.enabled = false;
4781 if(typeof callback === "function")
4786 NETDATA.gaugeAnimation = function(state, status) {
4789 if(typeof status === 'boolean' && status === false)
4791 else if(typeof status === 'number')
4794 state.gauge_instance.animationSpeed = speed;
4795 state.___gaugeOld__.speed = speed;
4798 NETDATA.gaugeSet = function(state, value, min, max) {
4799 if(typeof value !== 'number') value = 0;
4800 if(typeof min !== 'number') min = 0;
4801 if(typeof max !== 'number') max = 0;
4802 if(value > max) max = value;
4803 if(value < min) min = value;
4812 // gauge.js has an issue if the needle
4813 // is smaller than min or larger than max
4814 // when we set the new values
4815 // the needle will go crazy
4817 // to prevent it, we always feed it
4818 // with a percentage, so that the needle
4819 // is always between min and max
4820 var pcent = (value - min) * 100 / (max - min);
4822 // these should never happen
4823 if(pcent < 0) pcent = 0;
4824 if(pcent > 100) pcent = 100;
4826 state.gauge_instance.set(pcent);
4828 state.___gaugeOld__.value = value;
4829 state.___gaugeOld__.min = min;
4830 state.___gaugeOld__.max = max;
4833 NETDATA.gaugeSetLabels = function(state, value, min, max) {
4834 if(state.___gaugeOld__.valueLabel !== value) {
4835 state.___gaugeOld__.valueLabel = value;
4836 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
4838 if(state.___gaugeOld__.minLabel !== min) {
4839 state.___gaugeOld__.minLabel = min;
4840 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
4842 if(state.___gaugeOld__.maxLabel !== max) {
4843 state.___gaugeOld__.maxLabel = max;
4844 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
4848 NETDATA.gaugeClearSelection = function(state) {
4849 if(typeof state.gaugeEvent !== 'undefined') {
4850 if(state.gaugeEvent.timer !== null)
4851 clearTimeout(state.gaugeEvent.timer);
4853 state.gaugeEvent.timer = null;
4856 if(state.isAutoRefreshed() === true && state.data !== null) {
4857 NETDATA.gaugeChartUpdate(state, state.data);
4860 NETDATA.gaugeAnimation(state, false);
4861 NETDATA.gaugeSet(state, null, null, null);
4862 NETDATA.gaugeSetLabels(state, null, null, null);
4865 NETDATA.gaugeAnimation(state, true);
4869 NETDATA.gaugeSetSelection = function(state, t) {
4870 if(state.timeIsVisible(t) !== true)
4871 return NETDATA.gaugeClearSelection(state);
4873 var slot = state.calculateRowForTime(t);
4874 if(slot < 0 || slot >= state.data.result.length)
4875 return NETDATA.gaugeClearSelection(state);
4877 if(typeof state.gaugeEvent === 'undefined') {
4878 state.gaugeEvent = {
4886 var value = state.data.result[state.data.result.length - 1 - slot];
4887 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
4890 state.gaugeEvent.value = value;
4891 state.gaugeEvent.max = max;
4892 state.gaugeEvent.min = min;
4893 NETDATA.gaugeSetLabels(state, value, min, max);
4895 if(state.gaugeEvent.timer === null) {
4896 NETDATA.gaugeAnimation(state, false);
4898 state.gaugeEvent.timer = setTimeout(function() {
4899 state.gaugeEvent.timer = null;
4900 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
4901 }, NETDATA.options.current.charts_selection_animation_delay);
4907 NETDATA.gaugeChartUpdate = function(state, data) {
4908 var value, min, max;
4910 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
4914 NETDATA.gaugeSetLabels(state, null, null, null);
4917 value = data.result[0];
4919 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
4920 if(value > max) max = value;
4921 NETDATA.gaugeSetLabels(state, value, min, max);
4924 NETDATA.gaugeSet(state, value, min, max);
4928 NETDATA.gaugeChartCreate = function(state, data) {
4929 var self = $(state.element);
4930 // var chart = $(state.element_chart);
4932 var value = data.result[0];
4933 var max = self.data('gauge-max-value') || null;
4934 var adjust = self.data('gauge-adjust') || null;
4935 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
4936 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
4937 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
4938 var stopColor = self.data('gauge-stop-color') || void 0;
4939 var generateGradient = self.data('gauge-generate-gradient') || false;
4943 state.gaugeMax = null;
4946 state.gaugeMax = max;
4948 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
4950 // case 'width': width = height * ratio; break;
4952 // default: height = width / ratio; break;
4954 //state.element.style.width = width.toString() + 'px';
4955 //state.element.style.height = height.toString() + 'px';
4960 lines: 12, // The number of lines to draw
4961 angle: 0.15, // The length of each line
4962 lineWidth: 0.44, // 0.44 The line thickness
4964 length: 0.8, // 0.9 The radius of the inner circle
4965 strokeWidth: 0.035, // The rotation offset
4966 color: pointerColor // Fill color
4968 colorStart: startColor, // Colors
4969 colorStop: stopColor, // just experiment with them
4970 strokeColor: strokeColor, // to see which ones work best for you
4972 generateGradient: generateGradient,
4976 if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
4977 options.percentColors = [
4978 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
4979 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
4980 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
4981 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
4982 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
4983 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
4984 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
4985 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
4986 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
4987 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
4988 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
4991 state.gauge_canvas = document.createElement('canvas');
4992 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
4993 state.gauge_canvas.className = 'gaugeChart';
4994 state.gauge_canvas.width = width;
4995 state.gauge_canvas.height = height;
4996 state.element_chart.appendChild(state.gauge_canvas);
4998 var valuefontsize = Math.floor(height / 6);
4999 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5000 state.gaugeChartLabel = document.createElement('span');
5001 state.gaugeChartLabel.className = 'gaugeChartLabel';
5002 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5003 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5004 state.element_chart.appendChild(state.gaugeChartLabel);
5006 var titlefontsize = Math.round(valuefontsize / 2);
5008 state.gaugeChartTitle = document.createElement('span');
5009 state.gaugeChartTitle.className = 'gaugeChartTitle';
5010 state.gaugeChartTitle.innerHTML = state.title;
5011 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5012 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5013 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5014 state.element_chart.appendChild(state.gaugeChartTitle);
5016 var unitfontsize = Math.round(titlefontsize * 0.9);
5017 state.gaugeChartUnits = document.createElement('span');
5018 state.gaugeChartUnits.className = 'gaugeChartUnits';
5019 state.gaugeChartUnits.innerHTML = state.units;
5020 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5021 state.element_chart.appendChild(state.gaugeChartUnits);
5023 state.gaugeChartMin = document.createElement('span');
5024 state.gaugeChartMin.className = 'gaugeChartMin';
5025 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5026 state.element_chart.appendChild(state.gaugeChartMin);
5028 state.gaugeChartMax = document.createElement('span');
5029 state.gaugeChartMax.className = 'gaugeChartMax';
5030 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5031 state.element_chart.appendChild(state.gaugeChartMax);
5033 // when we just re-create the chart
5034 // do not animate the first update
5036 if(typeof state.gauge_instance !== 'undefined')
5039 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5041 state.___gaugeOld__ = {
5050 // we will always feed a percentage
5051 state.gauge_instance.minValue = 0;
5052 state.gauge_instance.maxValue = 100;
5054 NETDATA.gaugeAnimation(state, animate);
5055 NETDATA.gaugeSet(state, value, 0, max);
5056 NETDATA.gaugeSetLabels(state, value, 0, max);
5057 NETDATA.gaugeAnimation(state, true);
5061 // ----------------------------------------------------------------------------------------------------------------
5062 // Charts Libraries Registration
5064 NETDATA.chartLibraries = {
5066 initialize: NETDATA.dygraphInitialize,
5067 create: NETDATA.dygraphChartCreate,
5068 update: NETDATA.dygraphChartUpdate,
5069 resize: function(state) {
5070 if(typeof state.dygraph_instance.resize === 'function')
5071 state.dygraph_instance.resize();
5073 setSelection: NETDATA.dygraphSetSelection,
5074 clearSelection: NETDATA.dygraphClearSelection,
5075 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5078 format: function(state) { return 'json'; },
5079 options: function(state) { return 'ms|flip'; },
5080 legend: function(state) {
5081 if(this.isSparkline(state) === false)
5082 return 'right-side';
5086 autoresize: function(state) { return true; },
5087 max_updates_to_recreate: function(state) { return 5000; },
5088 track_colors: function(state) { return true; },
5089 pixels_per_point: function(state) {
5090 if(this.isSparkline(state) === false)
5096 isSparkline: function(state) {
5097 if(typeof state.dygraph_sparkline === 'undefined') {
5098 var t = $(state.element).data('dygraph-theme');
5099 if(t === 'sparkline')
5100 state.dygraph_sparkline = true;
5102 state.dygraph_sparkline = false;
5104 return state.dygraph_sparkline;
5108 initialize: NETDATA.sparklineInitialize,
5109 create: NETDATA.sparklineChartCreate,
5110 update: NETDATA.sparklineChartUpdate,
5112 setSelection: undefined, // function(state, t) { return true; },
5113 clearSelection: undefined, // function(state) { return true; },
5114 toolboxPanAndZoom: null,
5117 format: function(state) { return 'array'; },
5118 options: function(state) { return 'flip|abs'; },
5119 legend: function(state) { return null; },
5120 autoresize: function(state) { return false; },
5121 max_updates_to_recreate: function(state) { return 5000; },
5122 track_colors: function(state) { return false; },
5123 pixels_per_point: function(state) { return 3; }
5126 initialize: NETDATA.peityInitialize,
5127 create: NETDATA.peityChartCreate,
5128 update: NETDATA.peityChartUpdate,
5130 setSelection: undefined, // function(state, t) { return true; },
5131 clearSelection: undefined, // function(state) { return true; },
5132 toolboxPanAndZoom: null,
5135 format: function(state) { return 'ssvcomma'; },
5136 options: function(state) { return 'null2zero|flip|abs'; },
5137 legend: function(state) { return null; },
5138 autoresize: function(state) { return false; },
5139 max_updates_to_recreate: function(state) { return 5000; },
5140 track_colors: function(state) { return false; },
5141 pixels_per_point: function(state) { return 3; }
5144 initialize: NETDATA.morrisInitialize,
5145 create: NETDATA.morrisChartCreate,
5146 update: NETDATA.morrisChartUpdate,
5148 setSelection: undefined, // function(state, t) { return true; },
5149 clearSelection: undefined, // function(state) { return true; },
5150 toolboxPanAndZoom: null,
5153 format: function(state) { return 'json'; },
5154 options: function(state) { return 'objectrows|ms'; },
5155 legend: function(state) { return null; },
5156 autoresize: function(state) { return false; },
5157 max_updates_to_recreate: function(state) { return 50; },
5158 track_colors: function(state) { return false; },
5159 pixels_per_point: function(state) { return 15; }
5162 initialize: NETDATA.googleInitialize,
5163 create: NETDATA.googleChartCreate,
5164 update: NETDATA.googleChartUpdate,
5166 setSelection: undefined, //function(state, t) { return true; },
5167 clearSelection: undefined, //function(state) { return true; },
5168 toolboxPanAndZoom: null,
5171 format: function(state) { return 'datatable'; },
5172 options: function(state) { return ''; },
5173 legend: function(state) { return null; },
5174 autoresize: function(state) { return false; },
5175 max_updates_to_recreate: function(state) { return 300; },
5176 track_colors: function(state) { return false; },
5177 pixels_per_point: function(state) { return 4; }
5180 initialize: NETDATA.raphaelInitialize,
5181 create: NETDATA.raphaelChartCreate,
5182 update: NETDATA.raphaelChartUpdate,
5184 setSelection: undefined, // function(state, t) { return true; },
5185 clearSelection: undefined, // function(state) { return true; },
5186 toolboxPanAndZoom: null,
5189 format: function(state) { return 'json'; },
5190 options: function(state) { return ''; },
5191 legend: function(state) { return null; },
5192 autoresize: function(state) { return false; },
5193 max_updates_to_recreate: function(state) { return 5000; },
5194 track_colors: function(state) { return false; },
5195 pixels_per_point: function(state) { return 3; }
5198 initialize: NETDATA.c3Initialize,
5199 create: NETDATA.c3ChartCreate,
5200 update: NETDATA.c3ChartUpdate,
5202 setSelection: undefined, // function(state, t) { return true; },
5203 clearSelection: undefined, // function(state) { return true; },
5204 toolboxPanAndZoom: null,
5207 format: function(state) { return 'csvjsonarray'; },
5208 options: function(state) { return 'milliseconds'; },
5209 legend: function(state) { return null; },
5210 autoresize: function(state) { return false; },
5211 max_updates_to_recreate: function(state) { return 5000; },
5212 track_colors: function(state) { return false; },
5213 pixels_per_point: function(state) { return 15; }
5216 initialize: NETDATA.d3Initialize,
5217 create: NETDATA.d3ChartCreate,
5218 update: NETDATA.d3ChartUpdate,
5220 setSelection: undefined, // function(state, t) { return true; },
5221 clearSelection: undefined, // function(state) { return true; },
5222 toolboxPanAndZoom: null,
5225 format: function(state) { return 'json'; },
5226 options: function(state) { return ''; },
5227 legend: function(state) { return null; },
5228 autoresize: function(state) { return false; },
5229 max_updates_to_recreate: function(state) { return 5000; },
5230 track_colors: function(state) { return false; },
5231 pixels_per_point: function(state) { return 3; }
5234 initialize: NETDATA.easypiechartInitialize,
5235 create: NETDATA.easypiechartChartCreate,
5236 update: NETDATA.easypiechartChartUpdate,
5238 setSelection: NETDATA.easypiechartSetSelection,
5239 clearSelection: NETDATA.easypiechartClearSelection,
5240 toolboxPanAndZoom: null,
5243 format: function(state) { return 'array'; },
5244 options: function(state) { return 'absolute'; },
5245 legend: function(state) { return null; },
5246 autoresize: function(state) { return false; },
5247 max_updates_to_recreate: function(state) { return 5000; },
5248 track_colors: function(state) { return true; },
5249 pixels_per_point: function(state) { return 3; },
5253 initialize: NETDATA.gaugeInitialize,
5254 create: NETDATA.gaugeChartCreate,
5255 update: NETDATA.gaugeChartUpdate,
5257 setSelection: NETDATA.gaugeSetSelection,
5258 clearSelection: NETDATA.gaugeClearSelection,
5259 toolboxPanAndZoom: null,
5262 format: function(state) { return 'array'; },
5263 options: function(state) { return 'absolute'; },
5264 legend: function(state) { return null; },
5265 autoresize: function(state) { return false; },
5266 max_updates_to_recreate: function(state) { return 5000; },
5267 track_colors: function(state) { return true; },
5268 pixels_per_point: function(state) { return 3; },
5273 NETDATA.registerChartLibrary = function(library, url) {
5274 if(NETDATA.options.debug.libraries === true)
5275 console.log("registering chart library: " + library);
5277 NETDATA.chartLibraries[library].url = url;
5278 NETDATA.chartLibraries[library].initialized = true;
5279 NETDATA.chartLibraries[library].enabled = true;
5282 // ----------------------------------------------------------------------------------------------------------------
5285 NETDATA.requiredJs = [
5287 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5288 isAlreadyLoaded: function() {
5289 if(typeof $().emulateTransitionEnd == 'function')
5292 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5300 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5301 isAlreadyLoaded: function() { return false; }
5304 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5305 isAlreadyLoaded: function() { return false; }
5309 NETDATA.requiredCSS = [
5311 url: NETDATA.themes.current.bootstrap_css,
5312 isAlreadyLoaded: function() {
5313 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5320 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5321 isAlreadyLoaded: function() { return false; }
5324 url: NETDATA.themes.current.dashboard_css,
5325 isAlreadyLoaded: function() { return false; }
5328 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5329 isAlreadyLoaded: function() { return false; }
5333 NETDATA.loadRequiredJs = function(index, callback) {
5334 if(index >= NETDATA.requiredJs.length) {
5335 if(typeof callback === 'function')
5340 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5341 NETDATA.loadRequiredJs(++index, callback);
5345 if(NETDATA.options.debug.main_loop === true)
5346 console.log('loading ' + NETDATA.requiredJs[index].url);
5349 url: NETDATA.requiredJs[index].url,
5353 .success(function() {
5354 if(NETDATA.options.debug.main_loop === true)
5355 console.log('loaded ' + NETDATA.requiredJs[index].url);
5357 NETDATA.loadRequiredJs(++index, callback);
5360 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5364 NETDATA.loadRequiredCSS = function(index) {
5365 if(index >= NETDATA.requiredCSS.length)
5368 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5369 NETDATA.loadRequiredCSS(++index);
5373 if(NETDATA.options.debug.main_loop === true)
5374 console.log('loading ' + NETDATA.requiredCSS[index].url);
5376 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5377 NETDATA.loadRequiredCSS(++index);
5380 NETDATA.errorReset();
5381 NETDATA.loadRequiredCSS(0);
5383 NETDATA._loadjQuery(function() {
5384 NETDATA.loadRequiredJs(0, function() {
5385 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
5386 if(NETDATA.options.debug.main_loop === true)
5387 console.log('starting chart refresh thread');
5394 // window.NETDATA = NETDATA;
5395 // })(window, document);