1 // You can set the following variables before loading this script:
3 // var netdataNoDygraphs = true; // do not use dygraph
4 // var netdataNoSparklines = true; // do not use sparkline
5 // var netdataNoPeitys = true; // do not use peity
6 // var netdataNoGoogleCharts = true; // do not use google
7 // var netdataNoMorris = true; // do not use morris
8 // var netdataNoEasyPieChart = true; // do not use easy pie chart
9 // var netdataNoGauge = true; // do not use gauge.js
10 // var netdataNoD3 = true; // do not use D3
11 // var netdataNoC3 = true; // do not use C3
12 // var netdataNoBootstrap = true; // do not load bootstrap
13 // var netdataDontStart = true; // do not start the thread to process the charts
15 // You can also set the default netdata server, using the following.
16 // When this variable is not set, we assume the page is hosted on your
17 // netdata server already.
18 // var netdataServer = "http://yourhost:19999"; // set your NetData server
20 //(function(window, document, undefined) {
21 // fix IE issue with console
22 if(!window.console){ window.console = {log: function(){} }; }
25 var NETDATA = window.NETDATA || {};
27 // ----------------------------------------------------------------------------------------------------------------
28 // Detect the netdata server
30 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
31 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
32 NETDATA._scriptSource = function() {
35 if(typeof document.currentScript !== 'undefined') {
36 script = document.currentScript;
39 var all_scripts = document.getElementsByTagName('script');
40 script = all_scripts[all_scripts.length - 1];
43 if (typeof script.getAttribute.length !== 'undefined')
46 script = script.getAttribute('src', -1);
51 if(typeof netdataServer !== 'undefined')
52 NETDATA.serverDefault = netdataServer;
54 var s = NETDATA._scriptSource();
55 NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
58 if(NETDATA.serverDefault === null)
59 NETDATA.serverDefault = '';
60 else if(NETDATA.serverDefault.slice(-1) !== '/')
61 NETDATA.serverDefault += '/';
63 // default URLs for all the external files we need
64 // make them RELATIVE so that the whole thing can also be
65 // installed under a web server
66 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-1.12.0.min.js';
67 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity.min.js';
68 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline.min.js';
69 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart.min.js';
70 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge.min.js';
71 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined.js';
72 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter.js';
73 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-min.js';
74 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris.min.js';
75 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3.min.js';
76 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3.min.js';
77 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3.min.css';
78 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris.css';
79 NETDATA.google_js = 'https://www.google.com/jsapi';
83 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.min.css',
84 dashboard_css: NETDATA.serverDefault + 'dashboard.css',
85 background: '#FFFFFF',
86 foreground: '#000000',
89 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
90 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
91 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
92 '#329262', '#3B3EAC' ],
93 easypiechart_track: '#f0f0f0',
94 easypiechart_scale: '#dfe0e0',
95 gauge_pointer: '#C0C0C0',
96 gauge_stroke: '#F0F0F0',
100 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.slate.min.css',
101 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css',
102 background: '#272b30',
103 foreground: '#C8C8C8',
106 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
107 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
108 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
109 '#a6a479', '#a66da8' ],
111 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
112 '#3B3EAC', '#EE9911', '#BB44CC', '#C83E3E', '#990099', '#CC7700',
113 '#22AA99', '#109618', '#6633CC', '#DD4477', '#316395', '#8B0707',
114 '#329262', '#3B3EFF' ],
115 easypiechart_track: '#373b40',
116 easypiechart_scale: '#373b40',
117 gauge_pointer: '#474b50',
118 gauge_stroke: '#373b40',
119 gauge_gradient: false
123 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
124 NETDATA.themes.current = NETDATA.themes[netdataTheme];
126 NETDATA.themes.current = NETDATA.themes.default;
128 NETDATA.colors = NETDATA.themes.current.colors;
130 // these are the colors Google Charts are using
131 // we have them here to attempt emulate their look and feel on the other chart libraries
132 // http://there4.io/2012/05/02/google-chart-color-list/
133 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
134 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
135 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
137 // an alternative set
138 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
139 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
140 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
142 // ----------------------------------------------------------------------------------------------------------------
143 // the defaults for all charts
145 // if the user does not specify any of these, the following will be used
147 NETDATA.chartDefaults = {
148 host: NETDATA.serverDefault, // the server to get data from
149 width: '100%', // the chart width - can be null
150 height: '100%', // the chart height - can be null
151 min_width: null, // the chart minimum width - can be null
152 library: 'dygraph', // the graphing library to use
153 method: 'average', // the grouping method
154 before: 0, // panning
155 after: -600, // panning
156 pixels_per_point: 1, // the detail of the chart
157 fill_luminance: 0.8 // luminance of colors in solit areas
160 // ----------------------------------------------------------------------------------------------------------------
164 pauseCallback: null, // a callback when we are really paused
166 pause: false, // when enabled we don't auto-refresh the charts
168 targets: null, // an array of all the state objects that are
169 // currently active (independently of their
170 // viewport visibility)
172 updated_dom: true, // when true, the DOM has been updated with
173 // new elements we have to check.
175 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
176 // rendering charts continiously.
177 // used with .current.fast_render_timeframe
179 page_is_visible: true, // when true, this page is visible
181 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
182 // auto-refresher for some time (when a chart is
183 // performing pan or zoom, we need to stop refreshing
184 // all other charts, to have the maximum speed for
185 // rendering the chart that is panned or zoomed).
186 // Used with .current.global_pan_sync_time
188 last_resized: new Date().getTime(), // the timestamp of the last resize request
190 crossDomainAjax: false, // enable this to request crossDomain AJAX
192 last_page_scroll: 0, // the timestamp the last time the page was scrolled
194 // the current profile
195 // we may have many...
197 pixels_per_point: 1, // the minimum pixels per point for all charts
198 // increase this to speed javascript up
199 // each chart library has its own limit too
200 // the max of this and the chart library is used
201 // the final is calculated every time, so a change
202 // here will have immediate effect on the next chart
205 idle_between_charts: 100, // ms - how much time to wait between chart updates
207 fast_render_timeframe: 200, // ms - render continously until this time of continious
208 // rendering has been reached
209 // this setting is used to make it render e.g. 10
210 // charts at once, sleep idle_between_charts time
211 // and continue for another 10 charts.
213 idle_between_loops: 500, // ms - if all charts have been updated, wait this
214 // time before starting again.
216 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
218 idle_lost_focus: 500, // ms - when the window does not have focus, check
219 // if focus has been regained, every this time
221 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
222 // autorefreshing of charts is paused for this amount
225 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
226 // of time before setting up synchronized selections
229 sync_selection: true, // enable or disable selection sync
231 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
233 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
235 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
237 update_only_visible: true, // enable or disable visibility management
239 parallel_refresher: true, // enable parallel refresh of charts
241 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
243 destroy_on_hide: false, // destroy charts when they are not visible
245 show_help: true, // when enabled the charts will show some help
246 show_help_delay_show_ms: 500,
247 show_help_delay_hide_ms: 0,
249 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
251 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
252 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
254 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
256 smooth_plot: true, // enable smooth plot, where possible
258 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
260 color_fill_opacity_line: 1.0,
261 color_fill_opacity_area: 0.2,
262 color_fill_opacity_stacked: 0.8,
264 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
265 pan_and_zoom_factor_multiplier_control: 2.0,
266 pan_and_zoom_factor_multiplier_shift: 3.0,
267 pan_and_zoom_factor_multiplier_alt: 4.0,
269 setOptionCallback: function() { ; }
277 chart_data_url: false,
278 chart_errors: false, // FIXME
287 // ----------------------------------------------------------------------------------------------------------------
288 // local storage options
290 NETDATA.localStorage = {
293 callback: {} // only used for resetting back to defaults
296 NETDATA.localStorageGet = function(key, def, callback) {
299 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
300 NETDATA.localStorage.default[key.toString()] = def;
301 NETDATA.localStorage.callback[key.toString()] = callback;
304 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
306 // console.log('localStorage: loading "' + key.toString() + '"');
307 ret = localStorage.getItem(key.toString());
308 if(ret === null || ret === 'undefined') {
309 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
310 localStorage.setItem(key.toString(), JSON.stringify(def));
314 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
315 ret = JSON.parse(ret);
316 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
320 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
325 if(typeof ret === 'undefined' || ret === 'undefined') {
326 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
330 NETDATA.localStorage.current[key.toString()] = ret;
334 NETDATA.localStorageSet = function(key, value, callback) {
335 if(typeof value === 'undefined' || value === 'undefined') {
336 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
339 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
340 NETDATA.localStorage.default[key.toString()] = value;
341 NETDATA.localStorage.current[key.toString()] = value;
342 NETDATA.localStorage.callback[key.toString()] = callback;
345 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
346 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
348 localStorage.setItem(key.toString(), JSON.stringify(value));
351 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
355 NETDATA.localStorage.current[key.toString()] = value;
359 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
361 if(typeof obj[i] === 'object') {
362 //console.log('object ' + prefix + '.' + i.toString());
363 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
367 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
371 NETDATA.setOption = function(key, value) {
372 if(key.toString() === 'setOptionCallback') {
373 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
374 NETDATA.options.current[key.toString()] = value;
375 NETDATA.options.current.setOptionCallback();
378 else if(NETDATA.options.current[key.toString()] !== value) {
379 var name = 'options.' + key.toString();
381 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
382 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
384 //console.log(NETDATA.localStorage);
385 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
386 //console.log(NETDATA.options);
387 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
389 if(typeof NETDATA.options.current.setOptionCallback === 'function')
390 NETDATA.options.current.setOptionCallback();
396 NETDATA.getOption = function(key) {
397 return NETDATA.options.current[key.toString()];
400 // read settings from local storage
401 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
403 // always start with this option enabled.
404 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
406 NETDATA.resetOptions = function() {
407 for(var i in NETDATA.localStorage.default) {
408 var a = i.split('.');
410 if(a[0] === 'options') {
411 if(a[1] === 'setOptionCallback') continue;
412 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
413 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
415 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
417 else if(a[0] === 'chart_heights') {
418 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
419 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
425 // ----------------------------------------------------------------------------------------------------------------
427 if(NETDATA.options.debug.main_loop === true)
428 console.log('welcome to NETDATA');
430 NETDATA.onresize = function() {
431 NETDATA.options.last_resized = new Date().getTime();
435 NETDATA.onscroll = function() {
436 // console.log('onscroll');
438 NETDATA.options.last_page_scroll = new Date().getTime();
439 if(NETDATA.options.targets === null) return;
441 // when the user scrolls he sees that we have
442 // hidden all the not-visible charts
443 // using this little function we try to switch
444 // the charts back to visible quickly
445 var targets = NETDATA.options.targets;
446 var len = targets.length;
447 while(len--) targets[len].isVisible();
450 window.onresize = NETDATA.onresize;
451 window.onscroll = NETDATA.onscroll;
453 // ----------------------------------------------------------------------------------------------------------------
456 NETDATA.errorCodes = {
457 100: { message: "Cannot load chart library", alert: true },
458 101: { message: "Cannot load jQuery", alert: true },
459 402: { message: "Chart library not found", alert: false },
460 403: { message: "Chart library not enabled/is failed", alert: false },
461 404: { message: "Chart not found", alert: false }
463 NETDATA.errorLast = {
469 NETDATA.error = function(code, msg) {
470 NETDATA.errorLast.code = code;
471 NETDATA.errorLast.message = msg;
472 NETDATA.errorLast.datetime = new Date().getTime();
474 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
476 if(NETDATA.errorCodes[code].alert)
477 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
480 NETDATA.errorReset = function() {
481 NETDATA.errorLast.code = 0;
482 NETDATA.errorLast.message = "You are doing fine!";
483 NETDATA.errorLast.datetime = 0;
486 // ----------------------------------------------------------------------------------------------------------------
489 // When multiple charts need the same chart, we avoid downloading it
490 // multiple times (and having it in browser memory multiple time)
491 // by using this registry.
493 // Every time we download a chart definition, we save it here with .add()
494 // Then we try to get it back with .get(). If that fails, we download it.
496 NETDATA.chartRegistry = {
499 fixid: function(id) {
500 return id.replace(/:/g, "_").replace(/\//g, "_");
503 add: function(host, id, data) {
504 host = this.fixid(host);
507 if(typeof this.charts[host] === 'undefined')
508 this.charts[host] = {};
510 //console.log('added ' + host + '/' + id);
511 this.charts[host][id] = data;
514 get: function(host, id) {
515 host = this.fixid(host);
518 if(typeof this.charts[host] === 'undefined')
521 if(typeof this.charts[host][id] === 'undefined')
524 //console.log('cached ' + host + '/' + id);
525 return this.charts[host][id];
528 downloadAll: function(host, callback) {
529 while(host.slice(-1) === '/')
530 host = host.substring(0, host.length - 1);
535 url: host + '/api/v1/charts',
536 crossDomain: NETDATA.options.crossDomainAjax,
540 .done(function(data) {
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_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2165 else if (event.shiftKey)
2166 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2168 else if (event.altKey)
2169 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2172 return NETDATA.options.current.pan_and_zoom_factor;
2175 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2176 this.element.appendChild(this.element_legend_childs.toolbox);
2178 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2179 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2180 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2181 this.element_legend_childs.toolbox_left.onclick = function(e) {
2184 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2185 var before = that.view_before - step;
2186 var after = that.view_after - step;
2187 if(after >= that.netdata_first)
2188 that.library.toolboxPanAndZoom(that, after, before);
2190 if(NETDATA.options.current.show_help === true)
2191 $(this.element_legend_childs.toolbox_left).popover({
2196 placement: 'bottom',
2197 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2199 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>'
2203 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2204 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2205 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2206 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2208 NETDATA.resetAllCharts(that);
2210 if(NETDATA.options.current.show_help === true)
2211 $(this.element_legend_childs.toolbox_reset).popover({
2216 placement: 'bottom',
2217 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2218 title: 'Chart Reset',
2219 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>'
2222 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2223 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2224 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2225 this.element_legend_childs.toolbox_right.onclick = function(e) {
2227 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2228 var before = that.view_before + step;
2229 var after = that.view_after + step;
2230 if(before <= that.netdata_last)
2231 that.library.toolboxPanAndZoom(that, after, before);
2233 if(NETDATA.options.current.show_help === true)
2234 $(this.element_legend_childs.toolbox_right).popover({
2239 placement: 'bottom',
2240 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2242 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>'
2246 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2247 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2248 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2249 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2251 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2252 var before = that.view_before - dt;
2253 var after = that.view_after + dt;
2254 that.library.toolboxPanAndZoom(that, after, before);
2256 if(NETDATA.options.current.show_help === true)
2257 $(this.element_legend_childs.toolbox_zoomin).popover({
2262 placement: 'bottom',
2263 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2264 title: 'Chart Zoom In',
2265 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>'
2268 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2269 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2270 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2271 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2273 var dt = (((that.view_before - that.view_after) / (1.0 - (get_pan_and_zoom_step(e) * 0.8)) - (that.view_before - that.view_after)) / 2);
2274 var before = that.view_before + dt;
2275 var after = that.view_after - dt;
2277 that.library.toolboxPanAndZoom(that, after, before);
2279 if(NETDATA.options.current.show_help === true)
2280 $(this.element_legend_childs.toolbox_zoomout).popover({
2285 placement: 'bottom',
2286 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2287 title: 'Chart Zoom Out',
2288 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>'
2291 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2292 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2293 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2294 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2295 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2296 //e.preventDefault();
2297 //alert('clicked toolbox_volume on ' + that.id);
2301 this.element_legend_childs.toolbox = null;
2302 this.element_legend_childs.toolbox_left = null;
2303 this.element_legend_childs.toolbox_reset = null;
2304 this.element_legend_childs.toolbox_right = null;
2305 this.element_legend_childs.toolbox_zoomin = null;
2306 this.element_legend_childs.toolbox_zoomout = null;
2307 this.element_legend_childs.toolbox_volume = null;
2310 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2311 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2312 this.element.appendChild(this.element_legend_childs.resize_handler);
2313 if(NETDATA.options.current.show_help === true)
2314 $(this.element_legend_childs.resize_handler).popover({
2319 placement: 'bottom',
2320 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2321 title: 'Chart Resize',
2322 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>'
2326 this.element_legend_childs.resize_handler.onmousedown =
2328 that.resizeHandler(e);
2332 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2333 that.resizeHandler(e);
2336 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2337 this.element_legend.appendChild(this.element_legend_childs.title_date);
2339 this.element_legend.appendChild(document.createElement('br'));
2341 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2342 this.element_legend.appendChild(this.element_legend_childs.title_time);
2344 this.element_legend.appendChild(document.createElement('br'));
2346 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2347 this.element_legend.appendChild(this.element_legend_childs.title_units);
2349 this.element_legend.appendChild(document.createElement('br'));
2351 this.element_legend_childs.nano.className = 'netdata-legend-series';
2352 this.element_legend.appendChild(this.element_legend_childs.nano);
2354 content.className = 'netdata-legend-series-content';
2355 this.element_legend_childs.nano.appendChild(content);
2357 if(NETDATA.options.current.show_help === true)
2358 $(content).popover({
2363 placement: 'bottom',
2364 title: 'Chart Legend',
2365 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2366 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>'
2370 this.element_legend_childs = {
2372 resize_handler: null,
2375 toolbox_right: null,
2376 toolbox_reset: null,
2377 toolbox_zoomin: null,
2378 toolbox_zoomout: null,
2379 toolbox_volume: null,
2390 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2391 if(this.debug === true)
2392 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2394 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2395 genLabel(this, content, this.data.dimension_names[i], i);
2399 var tmp = new Array();
2400 for(var dim in this.chart.dimensions) {
2401 tmp.push(this.chart.dimensions[dim].name);
2402 genLabel(this, content, this.chart.dimensions[dim].name, i);
2404 this.element_legend_childs.series.labels_key = tmp.toString();
2405 if(this.debug === true)
2406 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2409 // create a hidden div to be used for hidding
2410 // the original legend of the chart library
2411 var el = document.createElement('div');
2412 if(this.element_legend !== null)
2413 this.element_legend.appendChild(el);
2414 el.style.display = 'none';
2416 this.element_legend_childs.hidden = document.createElement('div');
2417 el.appendChild(this.element_legend_childs.hidden);
2419 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2420 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2422 this.legendShowLatestValues();
2425 this.hasLegend = function() {
2426 if(typeof this.___hasLegendCache___ !== 'undefined')
2427 return this.___hasLegendCache___;
2430 if(this.library && this.library.legend(this) === 'right-side') {
2431 var legend = $(this.element).data('legend') || 'yes';
2432 if(legend === 'yes') leg = true;
2435 this.___hasLegendCache___ = leg;
2439 this.legendWidth = function() {
2440 return (this.hasLegend())?140:0;
2443 this.legendHeight = function() {
2444 return $(this.element).height();
2447 this.chartWidth = function() {
2448 return $(this.element).width() - this.legendWidth();
2451 this.chartHeight = function() {
2452 return $(this.element).height();
2455 this.chartPixelsPerPoint = function() {
2456 // force an options provided detail
2457 var px = this.pixels_per_point;
2459 if(this.library && px < this.library.pixels_per_point(this))
2460 px = this.library.pixels_per_point(this);
2462 if(px < NETDATA.options.current.pixels_per_point)
2463 px = NETDATA.options.current.pixels_per_point;
2468 this.needsRecreation = function() {
2470 this.chart_created === true
2472 && this.library.autoresize() === false
2473 && this.tm.last_resized < NETDATA.options.last_resized
2477 this.chartURL = function() {
2478 var after, before, points_multiplier = 1;
2479 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2480 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2482 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2483 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2484 this.view_after = after * 1000;
2485 this.view_before = before * 1000;
2487 this.requested_padding = null;
2488 points_multiplier = 1;
2490 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2491 this.tm.pan_and_zoom_seq = 0;
2493 before = Math.round(this.current.force_before_ms / 1000);
2494 after = Math.round(this.current.force_after_ms / 1000);
2495 this.view_after = after * 1000;
2496 this.view_before = before * 1000;
2498 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2499 this.requested_padding = Math.round((before - after) / 2);
2500 after -= this.requested_padding;
2501 before += this.requested_padding;
2502 this.requested_padding *= 1000;
2503 points_multiplier = 2;
2506 this.current.force_before_ms = null;
2507 this.current.force_after_ms = null;
2510 this.tm.pan_and_zoom_seq = 0;
2512 before = this.before;
2514 this.view_after = after * 1000;
2515 this.view_before = before * 1000;
2517 this.requested_padding = null;
2518 points_multiplier = 1;
2521 this.requested_after = after * 1000;
2522 this.requested_before = before * 1000;
2524 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2526 // build the data URL
2527 this.data_url = this.host + this.chart.data_url;
2528 this.data_url += "&format=" + this.library.format();
2529 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2530 this.data_url += "&group=" + this.method;
2531 this.data_url += "&options=" + this.library.options(this);
2532 this.data_url += '|jsonwrap';
2534 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2535 this.data_url += '|nonzero';
2537 if(this.append_options !== null)
2538 this.data_url += '|' + this.append_options.toString();
2541 this.data_url += "&after=" + after.toString();
2544 this.data_url += "&before=" + before.toString();
2547 this.data_url += "&dimensions=" + this.dimensions;
2549 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2550 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2553 this.redrawChart = function() {
2554 if(this.data !== null)
2555 this.updateChartWithData(this.data);
2558 this.updateChartWithData = function(data) {
2559 if(this.debug === true)
2560 this.log('updateChartWithData() called.');
2562 this._updating = false;
2564 // this may force the chart to be re-created
2568 this.updates_counter++;
2569 this.updates_since_last_unhide++;
2570 this.updates_since_last_creation++;
2572 var started = new Date().getTime();
2574 // if the result is JSON, find the latest update-every
2575 this.data_update_every = data.view_update_every * 1000;
2576 this.data_after = data.after * 1000;
2577 this.data_before = data.before * 1000;
2578 this.netdata_first = data.first_entry * 1000;
2579 this.netdata_last = data.last_entry * 1000;
2580 this.data_points = data.points;
2583 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2584 if(this.view_after < this.data_after) {
2585 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2586 this.view_after = this.data_after;
2589 if(this.view_before > this.data_before) {
2590 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2591 this.view_before = this.data_before;
2595 this.view_after = this.data_after;
2596 this.view_before = this.data_before;
2599 if(this.debug === true) {
2600 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2602 if(this.current.force_after_ms)
2603 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2605 this.log('STATUS: forced : unset');
2607 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2608 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2609 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2610 this.log('STATUS: points : ' + (this.data_points).toString());
2613 if(this.data_points === 0) {
2618 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2619 if(this.debug === true)
2620 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2622 this.chart_created = false;
2625 // check and update the legend
2626 this.legendUpdateDOM();
2628 if(this.chart_created === true
2629 && typeof this.library.update === 'function') {
2631 if(this.debug === true)
2632 this.log('updating chart...');
2634 if(callChartLibraryUpdateSafely(data) === false)
2638 if(this.debug === true)
2639 this.log('creating chart...');
2641 if(callChartLibraryCreateSafely(data) === false)
2645 this.legendShowLatestValues();
2646 if(this.selected === true)
2647 NETDATA.globalSelectionSync.stop();
2649 // update the performance counters
2650 var now = new Date().getTime();
2651 this.tm.last_updated = now;
2653 // don't update last_autorefreshed if this chart is
2654 // forced to be updated with global PanAndZoom
2655 if(NETDATA.globalPanAndZoom.isActive())
2656 this.tm.last_autorefreshed = 0;
2658 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes)
2659 this.tm.last_autorefreshed = Math.round(now / this.data_update_every) * this.data_update_every;
2661 this.tm.last_autorefreshed = now;
2664 this.refresh_dt_ms = now - started;
2665 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2667 if(this.refresh_dt_element !== null)
2668 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
2671 this.updateChart = function(callback) {
2672 if(this.debug === true)
2673 this.log('updateChart() called.');
2675 if(this._updating === true) {
2676 if(this.debug === true)
2677 this.log('I am already updating...');
2679 if(typeof callback === 'function') callback();
2683 // due to late initialization of charts and libraries
2684 // we need to check this too
2685 if(this.enabled === false) {
2686 if(this.debug === true)
2687 this.log('I am not enabled');
2689 if(typeof callback === 'function') callback();
2693 if(canBeRendered() === false) {
2694 if(typeof callback === 'function') callback();
2698 if(this.chart === null) {
2699 this.getChart(function() { that.updateChart(callback); });
2703 if(this.library.initialized === false) {
2704 if(this.library.enabled === true) {
2705 this.library.initialize(function() { that.updateChart(callback); });
2709 error('chart library "' + this.library_name + '" is not available.');
2710 if(typeof callback === 'function') callback();
2715 this.clearSelection();
2718 if(this.debug === true)
2719 this.log('updating from ' + this.data_url);
2721 this._updating = true;
2723 this.xhr = $.ajax( {
2725 crossDomain: NETDATA.options.crossDomainAjax,
2729 .success(function(data) {
2730 if(that.debug === true)
2731 that.log('data received. updating chart.');
2733 that.updateChartWithData(data);
2736 error('data download failed for url: ' + that.data_url);
2738 .always(function() {
2739 that._updating = false;
2740 if(typeof callback === 'function') callback();
2746 this.isVisible = function(nocache) {
2747 if(typeof nocache === 'undefined')
2750 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
2752 // caching - we do not evaluate the charts visibility
2753 // if the page has not been scrolled since the last check
2754 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2755 return this.___isVisible___;
2757 this.tm.last_visible_check = new Date().getTime();
2759 var wh = window.innerHeight;
2760 var x = this.element.getBoundingClientRect();
2764 if(x.width === 0 || x.height === 0) {
2766 this.___isVisible___ = false;
2767 return this.___isVisible___;
2770 if(x.top < 0 && -x.top > x.height) {
2771 // the chart is entirely above
2772 ret = -x.top - x.height;
2774 else if(x.top > wh) {
2775 // the chart is entirely below
2779 if(ret > tolerance) {
2780 // the chart is too far
2783 this.___isVisible___ = false;
2784 return this.___isVisible___;
2787 // the chart is inside or very close
2790 this.___isVisible___ = true;
2791 return this.___isVisible___;
2795 this.isAutoRefreshed = function() {
2796 return (this.current.autorefresh);
2799 this.canBeAutoRefreshed = function() {
2800 var now = new Date().getTime();
2802 if(this.enabled === false) {
2803 if(this.debug === true)
2804 this.log('I am not enabled');
2809 if(this.library === null || this.library.enabled === false) {
2810 error('charting library "' + this.library_name + '" is not available');
2811 if(this.debug === true)
2812 this.log('My chart library ' + this.library_name + ' is not available');
2817 if(this.isVisible() === false) {
2818 if(NETDATA.options.debug.visibility === true || this.debug === true)
2819 this.log('I am not visible');
2824 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
2825 if(this.debug === true)
2826 this.log('timed force update detected - allowing this update');
2828 this.current.force_update_at = 0;
2832 if(this.isAutoRefreshed() === true) {
2833 // allow the first update, even if the page is not visible
2834 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2835 if(NETDATA.options.debug.focus === true || this.debug === true)
2836 this.log('canBeAutoRefreshed(): page does not have focus');
2841 if(this.needsRecreation() === true) {
2842 if(this.debug === true)
2843 this.log('canBeAutoRefreshed(): needs re-creation.');
2848 // options valid only for autoRefresh()
2849 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
2850 if(NETDATA.globalPanAndZoom.isActive()) {
2851 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
2852 if(this.debug === true)
2853 this.log('canBeAutoRefreshed(): global panning: I need an update.');
2858 if(this.debug === true)
2859 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
2865 if(this.selected === true) {
2866 if(this.debug === true)
2867 this.log('canBeAutoRefreshed(): I have a selection in place.');
2872 if(this.paused === true) {
2873 if(this.debug === true)
2874 this.log('canBeAutoRefreshed(): I am paused.');
2879 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
2880 if(this.debug === true)
2881 this.log('canBeAutoRefreshed(): It is time to update me.');
2891 this.autoRefresh = function(callback) {
2892 if(this.canBeAutoRefreshed() === true) {
2893 this.updateChart(callback);
2896 if(typeof callback !== 'undefined')
2901 this._defaultsFromDownloadedChart = function(chart) {
2903 this.chart_url = chart.url;
2904 this.data_update_every = chart.update_every * 1000;
2905 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2906 this.tm.last_info_downloaded = new Date().getTime();
2908 if(this.title === null)
2909 this.title = chart.title;
2911 if(this.units === null)
2912 this.units = chart.units;
2915 // fetch the chart description from the netdata server
2916 this.getChart = function(callback) {
2917 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
2919 this._defaultsFromDownloadedChart(this.chart);
2920 if(typeof callback === 'function') callback();
2923 this.chart_url = "/api/v1/chart?chart=" + this.id;
2925 if(this.debug === true)
2926 this.log('downloading ' + this.chart_url);
2929 url: this.host + this.chart_url,
2930 crossDomain: NETDATA.options.crossDomainAjax,
2934 .done(function(chart) {
2935 chart.url = that.chart_url;
2936 that._defaultsFromDownloadedChart(chart);
2937 NETDATA.chartRegistry.add(that.host, that.id, chart);
2940 NETDATA.error(404, that.chart_url);
2941 error('chart not found on url "' + that.chart_url + '"');
2943 .always(function() {
2944 if(typeof callback === 'function') callback();
2949 // ============================================================================================================
2955 NETDATA.resetAllCharts = function(state) {
2956 // first clear the global selection sync
2957 // to make sure no chart is in selected state
2958 state.globalSelectionSyncStop();
2960 // there are 2 possibilities here
2961 // a. state is the global Pan and Zoom master
2962 // b. state is not the global Pan and Zoom master
2964 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
2967 // clear the global Pan and Zoom
2968 // this will also refresh the master
2969 // and unblock any charts currently mirroring the master
2970 NETDATA.globalPanAndZoom.clearMaster();
2972 // if we were not the master, reset our status too
2973 // this is required because most probably the mouse
2974 // is over this chart, blocking it from auto-refreshing
2975 if(master === false && (state.paused === true || state.selected === true))
2979 // get or create a chart state, given a DOM element
2980 NETDATA.chartState = function(element) {
2981 var state = $(element).data('netdata-state-object') || null;
2982 if(state === null) {
2983 state = new chartState(element);
2984 $(element).data('netdata-state-object', state);
2989 // ----------------------------------------------------------------------------------------------------------------
2990 // Library functions
2992 // Load a script without jquery
2993 // This is used to load jquery - after it is loaded, we use jquery
2994 NETDATA._loadjQuery = function(callback) {
2995 if(typeof jQuery === 'undefined') {
2996 if(NETDATA.options.debug.main_loop === true)
2997 console.log('loading ' + NETDATA.jQuery);
2999 var script = document.createElement('script');
3000 script.type = 'text/javascript';
3001 script.async = true;
3002 script.src = NETDATA.jQuery;
3004 // script.onabort = onError;
3005 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3006 if(typeof callback === "function")
3007 script.onload = callback;
3009 var s = document.getElementsByTagName('script')[0];
3010 s.parentNode.insertBefore(script, s);
3012 else if(typeof callback === "function")
3016 NETDATA._loadCSS = function(filename) {
3017 // don't use jQuery here
3018 // styles are loaded before jQuery
3019 // to eliminate showing an unstyled page to the user
3021 var fileref = document.createElement("link");
3022 fileref.setAttribute("rel", "stylesheet");
3023 fileref.setAttribute("type", "text/css");
3024 fileref.setAttribute("href", filename);
3026 if (typeof fileref !== 'undefined')
3027 document.getElementsByTagName("head")[0].appendChild(fileref);
3030 NETDATA.colorHex2Rgb = function(hex) {
3031 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3032 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3033 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3034 return r + r + g + g + b + b;
3037 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3039 r: parseInt(result[1], 16),
3040 g: parseInt(result[2], 16),
3041 b: parseInt(result[3], 16)
3045 NETDATA.colorLuminance = function(hex, lum) {
3046 // validate hex string
3047 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3049 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3053 // convert to decimal and change luminosity
3054 var rgb = "#", c, i;
3055 for (i = 0; i < 3; i++) {
3056 c = parseInt(hex.substr(i*2,2), 16);
3057 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3058 rgb += ("00"+c).substr(c.length);
3064 NETDATA.guid = function() {
3066 return Math.floor((1 + Math.random()) * 0x10000)
3071 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3074 NETDATA.zeropad = function(x) {
3075 if(x > -10 && x < 10) return '0' + x.toString();
3076 else return x.toString();
3079 // user function to signal us the DOM has been
3081 NETDATA.updatedDom = function() {
3082 NETDATA.options.updated_dom = true;
3085 NETDATA.ready = function(callback) {
3086 NETDATA.options.pauseCallback = callback;
3089 NETDATA.pause = function(callback) {
3090 if(NETDATA.options.pause === true)
3093 NETDATA.options.pauseCallback = callback;
3096 NETDATA.unpause = function() {
3097 NETDATA.options.pauseCallback = null;
3098 NETDATA.options.updated_dom = true;
3099 NETDATA.options.pause = false;
3102 // ----------------------------------------------------------------------------------------------------------------
3104 // this is purely sequencial charts refresher
3105 // it is meant to be autonomous
3106 NETDATA.chartRefresherNoParallel = function(index) {
3107 if(NETDATA.options.debug.mail_loop === true)
3108 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3110 if(NETDATA.options.updated_dom === true) {
3111 // the dom has been updated
3112 // get the dom parts again
3113 NETDATA.parseDom(NETDATA.chartRefresher);
3116 if(index >= NETDATA.options.targets.length) {
3117 if(NETDATA.options.debug.main_loop === true)
3118 console.log('waiting to restart main loop...');
3120 NETDATA.options.auto_refresher_fast_weight = 0;
3122 setTimeout(function() {
3123 NETDATA.chartRefresher();
3124 }, NETDATA.options.current.idle_between_loops);
3127 var state = NETDATA.options.targets[index];
3129 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3130 if(NETDATA.options.debug.main_loop === true)
3131 console.log('fast rendering...');
3133 state.autoRefresh(function() {
3134 NETDATA.chartRefresherNoParallel(++index);
3138 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3139 NETDATA.options.auto_refresher_fast_weight = 0;
3141 setTimeout(function() {
3142 state.autoRefresh(function() {
3143 NETDATA.chartRefresherNoParallel(++index);
3145 }, NETDATA.options.current.idle_between_charts);
3150 // this is part of the parallel refresher
3151 // its cause is to refresh sequencially all the charts
3152 // that depend on chart library initialization
3153 // it will call the parallel refresher back
3154 // as soon as it sees a chart that its chart library
3156 NETDATA.chartRefresher_uninitialized = function() {
3157 if(NETDATA.options.updated_dom === true) {
3158 // the dom has been updated
3159 // get the dom parts again
3160 NETDATA.parseDom(NETDATA.chartRefresher);
3164 if(NETDATA.options.sequencial.length === 0)
3165 NETDATA.chartRefresher();
3167 var state = NETDATA.options.sequencial.pop();
3168 if(state.library.initialized === true)
3169 NETDATA.chartRefresher();
3171 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3175 NETDATA.chartRefresherWaitTime = function() {
3176 return NETDATA.options.current.idle_parallel_loops;
3179 // the default refresher
3180 // it will create 2 sets of charts:
3181 // - the ones that can be refreshed in parallel
3182 // - the ones that depend on something else
3183 // the first set will be executed in parallel
3184 // the second will be given to NETDATA.chartRefresher_uninitialized()
3185 NETDATA.chartRefresher = function() {
3186 if(NETDATA.options.pause === true) {
3187 // console.log('auto-refresher is paused');
3188 setTimeout(NETDATA.chartRefresher,
3189 NETDATA.chartRefresherWaitTime());
3193 if(typeof NETDATA.options.pauseCallback === 'function') {
3194 // console.log('auto-refresher is calling pauseCallback');
3195 NETDATA.options.pause = true;
3196 NETDATA.options.pauseCallback();
3197 NETDATA.chartRefresher();
3201 if(NETDATA.options.current.parallel_refresher === false) {
3202 NETDATA.chartRefresherNoParallel(0);
3206 if(NETDATA.options.updated_dom === true) {
3207 // the dom has been updated
3208 // get the dom parts again
3209 NETDATA.parseDom(NETDATA.chartRefresher);
3213 var parallel = new Array();
3214 var targets = NETDATA.options.targets;
3215 var len = targets.length;
3217 if(targets[len].isVisible() === false)
3220 var state = targets[len];
3221 if(state.library.initialized === false) {
3222 if(state.library.enabled === true) {
3223 state.library.initialize(NETDATA.chartRefresher);
3227 state.error('chart library "' + state.library_name + '" is not enabled.');
3231 parallel.unshift(state);
3234 if(parallel.length > 0) {
3235 var parallel_jobs = parallel.length;
3237 // this will execute the jobs in parallel
3238 $(parallel).each(function() {
3239 this.autoRefresh(function() {
3242 if(parallel_jobs === 0) {
3243 setTimeout(NETDATA.chartRefresher,
3244 NETDATA.chartRefresherWaitTime());
3250 setTimeout(NETDATA.chartRefresher,
3251 NETDATA.chartRefresherWaitTime());
3255 NETDATA.parseDom = function(callback) {
3256 NETDATA.options.last_page_scroll = new Date().getTime();
3257 NETDATA.options.updated_dom = false;
3259 var targets = $('div[data-netdata]'); //.filter(':visible');
3261 if(NETDATA.options.debug.main_loop === true)
3262 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3264 NETDATA.options.targets = new Array();
3265 var len = targets.length;
3267 // the initialization will take care of sizing
3268 // and the "loading..." message
3269 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3272 if(typeof callback === 'function') callback();
3275 // this is the main function - where everything starts
3276 NETDATA.start = function() {
3277 // this should be called only once
3279 NETDATA.options.page_is_visible = true;
3281 $(window).blur(function() {
3282 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3283 NETDATA.options.page_is_visible = false;
3284 if(NETDATA.options.debug.focus === true)
3285 console.log('Lost Focus!');
3289 $(window).focus(function() {
3290 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3291 NETDATA.options.page_is_visible = true;
3292 if(NETDATA.options.debug.focus === true)
3293 console.log('Focus restored!');
3297 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3298 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3299 NETDATA.options.page_is_visible = false;
3300 if(NETDATA.options.debug.focus === true)
3301 console.log('Document has no focus!');
3305 // bootstrap tab switching
3306 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3308 // bootstrap modal switching
3309 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3310 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3312 NETDATA.parseDom(NETDATA.chartRefresher);
3315 // ----------------------------------------------------------------------------------------------------------------
3318 NETDATA.peityInitialize = function(callback) {
3319 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3321 url: NETDATA.peity_js,
3326 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3329 NETDATA.chartLibraries.peity.enabled = false;
3330 NETDATA.error(100, NETDATA.peity_js);
3332 .always(function() {
3333 if(typeof callback === "function")
3338 NETDATA.chartLibraries.peity.enabled = false;
3339 if(typeof callback === "function")
3344 NETDATA.peityChartUpdate = function(state, data) {
3345 state.peity_instance.innerHTML = data.result;
3347 if(state.peity_options.stroke !== state.chartColors()[0]) {
3348 state.peity_options.stroke = state.chartColors()[0];
3349 if(state.chart.chart_type === 'line')
3350 state.peity_options.fill = NETDATA.themes.current.background;
3352 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3355 $(state.peity_instance).peity('line', state.peity_options);
3359 NETDATA.peityChartCreate = function(state, data) {
3360 state.peity_instance = document.createElement('div');
3361 state.element_chart.appendChild(state.peity_instance);
3363 var self = $(state.element);
3364 state.peity_options = {
3365 stroke: NETDATA.themes.current.foreground,
3366 strokeWidth: self.data('peity-strokewidth') || 1,
3367 width: state.chartWidth(),
3368 height: state.chartHeight(),
3369 fill: NETDATA.themes.current.foreground
3372 NETDATA.peityChartUpdate(state, data);
3376 // ----------------------------------------------------------------------------------------------------------------
3379 NETDATA.sparklineInitialize = function(callback) {
3380 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3382 url: NETDATA.sparkline_js,
3387 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3390 NETDATA.chartLibraries.sparkline.enabled = false;
3391 NETDATA.error(100, NETDATA.sparkline_js);
3393 .always(function() {
3394 if(typeof callback === "function")
3399 NETDATA.chartLibraries.sparkline.enabled = false;
3400 if(typeof callback === "function")
3405 NETDATA.sparklineChartUpdate = function(state, data) {
3406 state.sparkline_options.width = state.chartWidth();
3407 state.sparkline_options.height = state.chartHeight();
3409 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3413 NETDATA.sparklineChartCreate = function(state, data) {
3414 var self = $(state.element);
3415 var type = self.data('sparkline-type') || 'line';
3416 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3417 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3418 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3419 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3420 var composite = self.data('sparkline-composite') || undefined;
3421 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3422 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3423 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3424 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3425 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3426 var spotColor = self.data('sparkline-spotcolor') || undefined;
3427 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3428 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3429 var spotRadius = self.data('sparkline-spotradius') || undefined;
3430 var valueSpots = self.data('sparkline-valuespots') || undefined;
3431 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3432 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3433 var lineWidth = self.data('sparkline-linewidth') || undefined;
3434 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3435 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3436 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3437 var xvalues = self.data('sparkline-xvalues') || undefined;
3438 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3439 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3440 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3441 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3442 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3443 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3444 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3445 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3446 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3447 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3448 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3449 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3450 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3451 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3452 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3453 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3454 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3455 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3456 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3457 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3458 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3459 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3461 state.sparkline_options = {
3463 lineColor: lineColor,
3464 fillColor: fillColor,
3465 chartRangeMin: chartRangeMin,
3466 chartRangeMax: chartRangeMax,
3467 composite: composite,
3468 enableTagOptions: enableTagOptions,
3469 tagOptionPrefix: tagOptionPrefix,
3470 tagValuesAttribute: tagValuesAttribute,
3471 disableHiddenCheck: disableHiddenCheck,
3472 defaultPixelsPerValue: defaultPixelsPerValue,
3473 spotColor: spotColor,
3474 minSpotColor: minSpotColor,
3475 maxSpotColor: maxSpotColor,
3476 spotRadius: spotRadius,
3477 valueSpots: valueSpots,
3478 highlightSpotColor: highlightSpotColor,
3479 highlightLineColor: highlightLineColor,
3480 lineWidth: lineWidth,
3481 normalRangeMin: normalRangeMin,
3482 normalRangeMax: normalRangeMax,
3483 drawNormalOnTop: drawNormalOnTop,
3485 chartRangeClip: chartRangeClip,
3486 chartRangeMinX: chartRangeMinX,
3487 chartRangeMaxX: chartRangeMaxX,
3488 disableInteraction: disableInteraction,
3489 disableTooltips: disableTooltips,
3490 disableHighlight: disableHighlight,
3491 highlightLighten: highlightLighten,
3492 highlightColor: highlightColor,
3493 tooltipContainer: tooltipContainer,
3494 tooltipClassname: tooltipClassname,
3495 tooltipChartTitle: state.title,
3496 tooltipFormat: tooltipFormat,
3497 tooltipPrefix: tooltipPrefix,
3498 tooltipSuffix: tooltipSuffix,
3499 tooltipSkipNull: tooltipSkipNull,
3500 tooltipValueLookups: tooltipValueLookups,
3501 tooltipFormatFieldlist: tooltipFormatFieldlist,
3502 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3503 numberFormatter: numberFormatter,
3504 numberDigitGroupSep: numberDigitGroupSep,
3505 numberDecimalMark: numberDecimalMark,
3506 numberDigitGroupCount: numberDigitGroupCount,
3507 animatedZooms: animatedZooms,
3508 width: state.chartWidth(),
3509 height: state.chartHeight()
3512 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3516 // ----------------------------------------------------------------------------------------------------------------
3523 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3524 if(after < state.netdata_first)
3525 after = state.netdata_first;
3527 if(before > state.netdata_last)
3528 before = state.netdata_last;
3530 state.setMode('zoom');
3531 state.globalSelectionSyncStop();
3532 state.globalSelectionSyncDelay();
3533 state.dygraph_user_action = true;
3534 state.dygraph_force_zoom = true;
3535 state.updateChartPanOrZoom(after, before);
3536 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3539 NETDATA.dygraphSetSelection = function(state, t) {
3540 if(typeof state.dygraph_instance !== 'undefined') {
3541 var r = state.calculateRowForTime(t);
3543 state.dygraph_instance.setSelection(r);
3545 state.dygraph_instance.clearSelection();
3546 state.legendShowUndefined();
3553 NETDATA.dygraphClearSelection = function(state, t) {
3554 if(typeof state.dygraph_instance !== 'undefined') {
3555 state.dygraph_instance.clearSelection();
3560 NETDATA.dygraphSmoothInitialize = function(callback) {
3562 url: NETDATA.dygraph_smooth_js,
3567 NETDATA.dygraph.smooth = true;
3568 smoothPlotter.smoothing = 0.3;
3571 NETDATA.dygraph.smooth = false;
3573 .always(function() {
3574 if(typeof callback === "function")
3579 NETDATA.dygraphInitialize = function(callback) {
3580 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3582 url: NETDATA.dygraph_js,
3587 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3590 NETDATA.chartLibraries.dygraph.enabled = false;
3591 NETDATA.error(100, NETDATA.dygraph_js);
3593 .always(function() {
3594 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3595 NETDATA.dygraphSmoothInitialize(callback);
3596 else if(typeof callback === "function")
3601 NETDATA.chartLibraries.dygraph.enabled = false;
3602 if(typeof callback === "function")
3607 NETDATA.dygraphChartUpdate = function(state, data) {
3608 var dygraph = state.dygraph_instance;
3610 if(typeof dygraph === 'undefined')
3611 return NETDATA.dygraphChartCreate(state, data);
3613 // when the chart is not visible, and hidden
3614 // if there is a window resize, dygraph detects
3615 // its element size as 0x0.
3616 // this will make it re-appear properly
3618 if(state.tm.last_unhidden > state.dygraph_last_rendered)
3622 file: data.result.data,
3623 colors: state.chartColors(),
3624 labels: data.result.labels,
3625 labelsDivWidth: state.chartWidth() - 70,
3626 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3629 if(state.dygraph_force_zoom === true) {
3630 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3631 state.log('dygraphChartUpdate() forced zoom update');
3633 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3634 options.valueRange = null;
3635 options.isZoomedIgnoreProgrammaticZoom = true;
3636 state.dygraph_force_zoom = false;
3638 else if(state.current.name !== 'auto') {
3639 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3640 state.log('dygraphChartUpdate() loose update');
3643 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3644 state.log('dygraphChartUpdate() strict update');
3646 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
3647 options.valueRange = null;
3648 options.isZoomedIgnoreProgrammaticZoom = true;
3651 if(state.dygraph_smooth_eligible === true) {
3652 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
3653 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
3654 NETDATA.dygraphChartCreate(state, data);
3659 dygraph.updateOptions(options);
3661 state.dygraph_last_rendered = new Date().getTime();
3665 NETDATA.dygraphChartCreate = function(state, data) {
3666 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3667 state.log('dygraphChartCreate()');
3669 var self = $(state.element);
3671 var chart_type = state.chart.chart_type;
3672 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
3673 chart_type = self.data('dygraph-type') || chart_type;
3675 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
3676 smooth = self.data('dygraph-smooth') || smooth;
3678 if(NETDATA.dygraph.smooth === false)
3681 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
3682 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
3684 state.dygraph_options = {
3685 colors: self.data('dygraph-colors') || state.chartColors(),
3687 // leave a few pixels empty on the right of the chart
3688 rightGap: self.data('dygraph-rightgap') || 5,
3689 showRangeSelector: self.data('dygraph-showrangeselector') || false,
3690 showRoller: self.data('dygraph-showroller') || false,
3692 title: self.data('dygraph-title') || state.title,
3693 titleHeight: self.data('dygraph-titleheight') || 19,
3695 legend: self.data('dygraph-legend') || 'always', // 'onmouseover',
3696 labels: data.result.labels,
3697 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
3698 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
3699 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
3700 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
3701 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
3704 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
3705 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
3707 ylabel: state.units,
3708 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
3710 // the function to plot the chart
3713 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
3714 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
3715 strokePattern: self.data('dygraph-strokepattern') || undefined,
3717 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
3718 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
3719 drawPoints: self.data('dygraph-drawpoints') || false,
3721 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
3722 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
3724 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
3725 pointSize: self.data('dygraph-pointsize') || 1,
3727 // enabling this makes the chart with little square lines
3728 stepPlot: self.data('dygraph-stepplot') || false,
3730 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
3731 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
3732 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
3734 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
3735 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
3736 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
3737 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
3739 drawAxis: self.data('dygraph-drawaxis') || true,
3740 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
3741 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
3742 axisLineWidth: self.data('dygraph-axislinewidth') || 0.3,
3744 drawGrid: self.data('dygraph-drawgrid') || true,
3745 drawXGrid: self.data('dygraph-drawxgrid') || undefined,
3746 drawYGrid: self.data('dygraph-drawygrid') || undefined,
3747 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
3748 gridLineWidth: self.data('dygraph-gridlinewidth') || 0.3,
3749 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
3751 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
3752 sigFigs: self.data('dygraph-sigfigs') || null,
3753 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
3754 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
3756 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
3757 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
3758 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
3760 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
3761 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
3765 ticker: Dygraph.dateTicker,
3766 axisLabelFormatter: function (d, gran) {
3767 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3769 valueFormatter: function (ms) {
3770 var d = new Date(ms);
3771 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
3772 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
3777 valueFormatter: function (x) {
3778 // we format legends with the state object
3779 // no need to do anything here
3780 // return (Math.round(x*100) / 100).toLocaleString();
3781 // return state.legendFormatValue(x);
3786 legendFormatter: function(data) {
3787 var elements = state.element_legend_childs;
3789 // if the hidden div is not there
3790 // we are not managing the legend
3791 if(elements.hidden === null) return;
3793 if (typeof data.x !== 'undefined') {
3794 state.legendSetDate(data.x);
3795 var i = data.series.length;
3797 var series = data.series[i];
3798 if(!series.isVisible) continue;
3799 state.legendSetLabelValue(series.label, series.y);
3805 drawCallback: function(dygraph, is_initial) {
3806 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
3807 state.dygraph_user_action = false;
3809 var x_range = dygraph.xAxisRange();
3810 var after = Math.round(x_range[0]);
3811 var before = Math.round(x_range[1]);
3813 if(NETDATA.options.debug.dygraph === true)
3814 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
3816 if(before <= state.netdata_last && after >= state.netdata_first)
3817 state.updateChartPanOrZoom(after, before);
3820 zoomCallback: function(minDate, maxDate, yRanges) {
3821 if(NETDATA.options.debug.dygraph === true)
3822 state.log('dygraphZoomCallback()');
3824 state.globalSelectionSyncStop();
3825 state.globalSelectionSyncDelay();
3826 state.setMode('zoom');
3828 // refresh it to the greatest possible zoom level
3829 state.dygraph_user_action = true;
3830 state.dygraph_force_zoom = true;
3831 state.updateChartPanOrZoom(minDate, maxDate);
3833 highlightCallback: function(event, x, points, row, seriesName) {
3834 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3835 state.log('dygraphHighlightCallback()');
3839 // there is a bug in dygraph when the chart is zoomed enough
3840 // the time it thinks is selected is wrong
3841 // here we calculate the time t based on the row number selected
3843 var t = state.data_after + row * state.data_update_every;
3844 // 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);
3846 state.globalSelectionSync(x);
3848 // fix legend zIndex using the internal structures of dygraph legend module
3849 // this works, but it is a hack!
3850 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
3852 unhighlightCallback: function(event) {
3853 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3854 state.log('dygraphUnhighlightCallback()');
3856 state.unpauseChart();
3857 state.globalSelectionSyncStop();
3859 interactionModel : {
3860 mousedown: function(event, dygraph, context) {
3861 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3862 state.log('interactionModel.mousedown()');
3864 state.dygraph_user_action = true;
3865 state.globalSelectionSyncStop();
3867 if(NETDATA.options.debug.dygraph === true)
3868 state.log('dygraphMouseDown()');
3870 // Right-click should not initiate a zoom.
3871 if(event.button && event.button === 2) return;
3873 context.initializeMouseDown(event, dygraph, context);
3875 if(event.button && event.button === 1) {
3876 if (event.altKey || event.shiftKey) {
3877 state.setMode('pan');
3878 state.globalSelectionSyncDelay();
3879 Dygraph.startPan(event, dygraph, context);
3882 state.setMode('zoom');
3883 state.globalSelectionSyncDelay();
3884 Dygraph.startZoom(event, dygraph, context);
3888 if (event.altKey || event.shiftKey) {
3889 state.setMode('zoom');
3890 state.globalSelectionSyncDelay();
3891 Dygraph.startZoom(event, dygraph, context);
3894 state.setMode('pan');
3895 state.globalSelectionSyncDelay();
3896 Dygraph.startPan(event, dygraph, context);
3900 mousemove: function(event, dygraph, context) {
3901 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3902 state.log('interactionModel.mousemove()');
3904 if(context.isPanning) {
3905 state.dygraph_user_action = true;
3906 state.globalSelectionSyncStop();
3907 state.globalSelectionSyncDelay();
3908 state.setMode('pan');
3909 Dygraph.movePan(event, dygraph, context);
3911 else if(context.isZooming) {
3912 state.dygraph_user_action = true;
3913 state.globalSelectionSyncStop();
3914 state.globalSelectionSyncDelay();
3915 state.setMode('zoom');
3916 Dygraph.moveZoom(event, dygraph, context);
3919 mouseup: function(event, dygraph, context) {
3920 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3921 state.log('interactionModel.mouseup()');
3923 if (context.isPanning) {
3924 state.dygraph_user_action = true;
3925 state.globalSelectionSyncDelay();
3926 Dygraph.endPan(event, dygraph, context);
3928 else if (context.isZooming) {
3929 state.dygraph_user_action = true;
3930 state.globalSelectionSyncDelay();
3931 Dygraph.endZoom(event, dygraph, context);
3934 click: function(event, dygraph, context) {
3935 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3936 state.log('interactionModel.click()');
3938 event.preventDefault();
3940 dblclick: function(event, dygraph, context) {
3941 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3942 state.log('interactionModel.dblclick()');
3943 NETDATA.resetAllCharts(state);
3945 mousewheel: function(event, dygraph, context) {
3946 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3947 state.log('interactionModel.mousewheel()');
3949 // Take the offset of a mouse event on the dygraph canvas and
3950 // convert it to a pair of percentages from the bottom left.
3951 // (Not top left, bottom is where the lower value is.)
3952 function offsetToPercentage(g, offsetX, offsetY) {
3953 // This is calculating the pixel offset of the leftmost date.
3954 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
3955 var yar0 = g.yAxisRange(0);
3957 // This is calculating the pixel of the higest value. (Top pixel)
3958 var yOffset = g.toDomCoords(null, yar0[1])[1];
3960 // x y w and h are relative to the corner of the drawing area,
3961 // so that the upper corner of the drawing area is (0, 0).
3962 var x = offsetX - xOffset;
3963 var y = offsetY - yOffset;
3965 // This is computing the rightmost pixel, effectively defining the
3967 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
3969 // This is computing the lowest pixel, effectively defining the height.
3970 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
3972 // Percentage from the left.
3973 var xPct = w === 0 ? 0 : (x / w);
3974 // Percentage from the top.
3975 var yPct = h === 0 ? 0 : (y / h);
3977 // The (1-) part below changes it from "% distance down from the top"
3978 // to "% distance up from the bottom".
3979 return [xPct, (1-yPct)];
3982 // Adjusts [x, y] toward each other by zoomInPercentage%
3983 // Split it so the left/bottom axis gets xBias/yBias of that change and
3984 // tight/top gets (1-xBias)/(1-yBias) of that change.
3986 // If a bias is missing it splits it down the middle.
3987 function zoomRange(g, zoomInPercentage, xBias, yBias) {
3988 xBias = xBias || 0.5;
3989 yBias = yBias || 0.5;
3991 function adjustAxis(axis, zoomInPercentage, bias) {
3992 var delta = axis[1] - axis[0];
3993 var increment = delta * zoomInPercentage;
3994 var foo = [increment * bias, increment * (1-bias)];
3996 return [ axis[0] + foo[0], axis[1] - foo[1] ];
3999 var yAxes = g.yAxisRanges();
4001 for (var i = 0; i < yAxes.length; i++) {
4002 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4005 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4008 if(event.altKey || event.shiftKey) {
4009 state.dygraph_user_action = true;
4011 state.globalSelectionSyncStop();
4012 state.globalSelectionSyncDelay();
4014 // http://dygraphs.com/gallery/interaction-api.js
4015 var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
4016 var percentage = normal / 50;
4018 if (!(event.offsetX && event.offsetY)){
4019 event.offsetX = event.layerX - event.target.offsetLeft;
4020 event.offsetY = event.layerY - event.target.offsetTop;
4023 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4024 var xPct = percentages[0];
4025 var yPct = percentages[1];
4027 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4029 var after = new_x_range[0];
4030 var before = new_x_range[1];
4032 var first = state.netdata_first + state.data_update_every;
4033 var last = state.netdata_last + state.data_update_every;
4036 after -= (before - last);
4043 state.setMode('zoom');
4044 if(state.updateChartPanOrZoom(after, before) === true)
4045 dygraph.updateOptions({ dateWindow: [ after, before ] });
4047 event.preventDefault();
4050 touchstart: function(event, dygraph, context) {
4051 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4052 state.log('interactionModel.touchstart()');
4054 state.dygraph_user_action = true;
4055 state.setMode('zoom');
4058 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4060 // we overwrite the touch directions at the end, to overwrite
4061 // the internal default of dygraphs
4062 context.touchDirections = { x: true, y: false };
4064 state.dygraph_last_touch_start = new Date().getTime();
4065 state.dygraph_last_touch_move = 0;
4067 if(typeof event.touches[0].pageX === 'number')
4068 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4070 state.dygraph_last_touch_page_x = 0;
4072 touchmove: function(event, dygraph, context) {
4073 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4074 state.log('interactionModel.touchmove()');
4076 state.dygraph_user_action = true;
4077 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4079 state.dygraph_last_touch_move = new Date().getTime();
4081 touchend: function(event, dygraph, context) {
4082 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4083 state.log('interactionModel.touchend()');
4085 state.dygraph_user_action = true;
4086 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4088 // if it didn't move, it is a selection
4089 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4090 // internal api of dygraphs
4091 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4092 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4093 if(NETDATA.dygraphSetSelection(state, t) === true)
4094 state.globalSelectionSync(t);
4097 // if it was double tap within double click time, reset the charts
4098 var now = new Date().getTime();
4099 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4100 if(state.dygraph_last_touch_move === 0) {
4101 var dt = now - state.dygraph_last_touch_end;
4102 if(dt <= NETDATA.options.current.double_click_speed)
4103 NETDATA.resetAllCharts(state);
4107 // remember the timestamp of the last touch end
4108 state.dygraph_last_touch_end = now;
4113 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4114 state.dygraph_options.drawGrid = false;
4115 state.dygraph_options.drawAxis = false;
4116 state.dygraph_options.title = undefined;
4117 state.dygraph_options.units = undefined;
4118 state.dygraph_options.ylabel = undefined;
4119 state.dygraph_options.yLabelWidth = 0;
4120 state.dygraph_options.labelsDivWidth = 120;
4121 state.dygraph_options.labelsDivStyles.width = '120px';
4122 state.dygraph_options.labelsSeparateLines = true;
4123 state.dygraph_options.rightGap = 0;
4126 if(smooth === true) {
4127 state.dygraph_smooth_eligible = true;
4129 if(NETDATA.options.current.smooth_plot === true)
4130 state.dygraph_options.plotter = smoothPlotter;
4132 else state.dygraph_smooth_eligible = false;
4134 state.dygraph_instance = new Dygraph(state.element_chart,
4135 data.result.data, state.dygraph_options);
4137 state.dygraph_force_zoom = false;
4138 state.dygraph_user_action = false;
4139 state.dygraph_last_rendered = new Date().getTime();
4143 // ----------------------------------------------------------------------------------------------------------------
4146 NETDATA.morrisInitialize = function(callback) {
4147 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4149 // morris requires raphael
4150 if(!NETDATA.chartLibraries.raphael.initialized) {
4151 if(NETDATA.chartLibraries.raphael.enabled) {
4152 NETDATA.raphaelInitialize(function() {
4153 NETDATA.morrisInitialize(callback);
4157 NETDATA.chartLibraries.morris.enabled = false;
4158 if(typeof callback === "function")
4163 NETDATA._loadCSS(NETDATA.morris_css);
4166 url: NETDATA.morris_js,
4171 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4174 NETDATA.chartLibraries.morris.enabled = false;
4175 NETDATA.error(100, NETDATA.morris_js);
4177 .always(function() {
4178 if(typeof callback === "function")
4184 NETDATA.chartLibraries.morris.enabled = false;
4185 if(typeof callback === "function")
4190 NETDATA.morrisChartUpdate = function(state, data) {
4191 state.morris_instance.setData(data.result.data);
4195 NETDATA.morrisChartCreate = function(state, data) {
4197 state.morris_options = {
4198 element: state.element_chart.id,
4199 data: data.result.data,
4201 ykeys: data.dimension_names,
4202 labels: data.dimension_names,
4208 continuousLine: false,
4209 behaveLikeLine: false
4212 if(state.chart.chart_type === 'line')
4213 state.morris_instance = new Morris.Line(state.morris_options);
4215 else if(state.chart.chart_type === 'area') {
4216 state.morris_options.behaveLikeLine = true;
4217 state.morris_instance = new Morris.Area(state.morris_options);
4220 state.morris_instance = new Morris.Area(state.morris_options);
4225 // ----------------------------------------------------------------------------------------------------------------
4228 NETDATA.raphaelInitialize = function(callback) {
4229 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4231 url: NETDATA.raphael_js,
4236 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4239 NETDATA.chartLibraries.raphael.enabled = false;
4240 NETDATA.error(100, NETDATA.raphael_js);
4242 .always(function() {
4243 if(typeof callback === "function")
4248 NETDATA.chartLibraries.raphael.enabled = false;
4249 if(typeof callback === "function")
4254 NETDATA.raphaelChartUpdate = function(state, data) {
4255 $(state.element_chart).raphael(data.result, {
4256 width: state.chartWidth(),
4257 height: state.chartHeight()
4263 NETDATA.raphaelChartCreate = function(state, data) {
4264 $(state.element_chart).raphael(data.result, {
4265 width: state.chartWidth(),
4266 height: state.chartHeight()
4272 // ----------------------------------------------------------------------------------------------------------------
4275 NETDATA.c3Initialize = function(callback) {
4276 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4279 if(!NETDATA.chartLibraries.d3.initialized) {
4280 if(NETDATA.chartLibraries.d3.enabled) {
4281 NETDATA.d3Initialize(function() {
4282 NETDATA.c3Initialize(callback);
4286 NETDATA.chartLibraries.c3.enabled = false;
4287 if(typeof callback === "function")
4292 NETDATA._loadCSS(NETDATA.c3_css);
4300 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4303 NETDATA.chartLibraries.c3.enabled = false;
4304 NETDATA.error(100, NETDATA.c3_js);
4306 .always(function() {
4307 if(typeof callback === "function")
4313 NETDATA.chartLibraries.c3.enabled = false;
4314 if(typeof callback === "function")
4319 NETDATA.c3ChartUpdate = function(state, data) {
4320 state.c3_instance.destroy();
4321 return NETDATA.c3ChartCreate(state, data);
4323 //state.c3_instance.load({
4324 // rows: data.result,
4331 NETDATA.c3ChartCreate = function(state, data) {
4333 state.element_chart.id = 'c3-' + state.uuid;
4334 // console.log('id = ' + state.element_chart.id);
4336 state.c3_instance = c3.generate({
4337 bindto: '#' + state.element_chart.id,
4339 width: state.chartWidth(),
4340 height: state.chartHeight()
4343 pattern: state.chartColors()
4348 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4354 format: function(x) {
4355 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4382 // console.log(state.c3_instance);
4387 // ----------------------------------------------------------------------------------------------------------------
4390 NETDATA.d3Initialize = function(callback) {
4391 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4398 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4401 NETDATA.chartLibraries.d3.enabled = false;
4402 NETDATA.error(100, NETDATA.d3_js);
4404 .always(function() {
4405 if(typeof callback === "function")
4410 NETDATA.chartLibraries.d3.enabled = false;
4411 if(typeof callback === "function")
4416 NETDATA.d3ChartUpdate = function(state, data) {
4420 NETDATA.d3ChartCreate = function(state, data) {
4424 // ----------------------------------------------------------------------------------------------------------------
4427 NETDATA.googleInitialize = function(callback) {
4428 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4430 url: NETDATA.google_js,
4435 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4436 google.load('visualization', '1.1', {
4437 'packages': ['corechart', 'controls'],
4438 'callback': callback
4442 NETDATA.chartLibraries.google.enabled = false;
4443 NETDATA.error(100, NETDATA.google_js);
4444 if(typeof callback === "function")
4449 NETDATA.chartLibraries.google.enabled = false;
4450 if(typeof callback === "function")
4455 NETDATA.googleChartUpdate = function(state, data) {
4456 var datatable = new google.visualization.DataTable(data.result);
4457 state.google_instance.draw(datatable, state.google_options);
4461 NETDATA.googleChartCreate = function(state, data) {
4462 var datatable = new google.visualization.DataTable(data.result);
4464 state.google_options = {
4465 colors: state.chartColors(),
4467 // do not set width, height - the chart resizes itself
4468 //width: state.chartWidth(),
4469 //height: state.chartHeight(),
4474 // title: "Time of Day",
4475 // format:'HH:mm:ss',
4476 viewWindowMode: 'maximized',
4488 viewWindowMode: 'pretty',
4503 focusTarget: 'category',
4510 titlePosition: 'out',
4521 curveType: 'function',
4526 switch(state.chart.chart_type) {
4528 state.google_options.vAxis.viewWindowMode = 'maximized';
4529 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4530 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4534 state.google_options.isStacked = true;
4535 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4536 state.google_options.vAxis.viewWindowMode = 'maximized';
4537 state.google_options.vAxis.minValue = null;
4538 state.google_options.vAxis.maxValue = null;
4539 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4544 state.google_options.lineWidth = 2;
4545 state.google_instance = new google.visualization.LineChart(state.element_chart);
4549 state.google_instance.draw(datatable, state.google_options);
4553 // ----------------------------------------------------------------------------------------------------------------
4555 NETDATA.percentFromValueMax = function(value, max) {
4556 if(value === null) value = 0;
4557 if(max < value) max = value;
4561 pcent = Math.round(value * 100 / max);
4562 if(pcent === 0 && value > 0) pcent = 1;
4568 // ----------------------------------------------------------------------------------------------------------------
4571 NETDATA.easypiechartInitialize = function(callback) {
4572 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
4574 url: NETDATA.easypiechart_js,
4579 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
4582 NETDATA.chartLibraries.easypiechart.enabled = false;
4583 NETDATA.error(100, NETDATA.easypiechart_js);
4585 .always(function() {
4586 if(typeof callback === "function")
4591 NETDATA.chartLibraries.easypiechart.enabled = false;
4592 if(typeof callback === "function")
4597 NETDATA.easypiechartClearSelection = function(state) {
4598 if(typeof state.easyPieChartEvent !== 'undefined') {
4599 if(state.easyPieChartEvent.timer !== null)
4600 clearTimeout(state.easyPieChartEvent.timer);
4602 state.easyPieChartEvent.timer = null;
4605 if(state.isAutoRefreshed() === true && state.data !== null) {
4606 NETDATA.easypiechartChartUpdate(state, state.data);
4609 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
4610 state.easyPieChart_instance.update(0);
4612 state.easyPieChart_instance.enableAnimation();
4617 NETDATA.easypiechartSetSelection = function(state, t) {
4618 if(state.timeIsVisible(t) !== true)
4619 return NETDATA.easypiechartClearSelection(state);
4621 var slot = state.calculateRowForTime(t);
4622 if(slot < 0 || slot >= state.data.result.length)
4623 return NETDATA.easypiechartClearSelection(state);
4625 if(typeof state.easyPieChartEvent === 'undefined') {
4626 state.easyPieChartEvent = {
4633 var value = state.data.result[state.data.result.length - 1 - slot];
4634 var max = (state.easyPieChartMax === null)?state.data.max:state.easyPieChartMax;
4635 var pcent = NETDATA.percentFromValueMax(value, max);
4637 state.easyPieChartEvent.value = value;
4638 state.easyPieChartEvent.pcent = pcent;
4639 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4641 if(state.easyPieChartEvent.timer === null) {
4642 state.easyPieChart_instance.disableAnimation();
4644 state.easyPieChartEvent.timer = setTimeout(function() {
4645 state.easyPieChartEvent.timer = null;
4646 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
4647 }, NETDATA.options.current.charts_selection_animation_delay);
4653 NETDATA.easypiechartChartUpdate = function(state, data) {
4654 var value, max, pcent;
4656 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
4662 value = data.result[0];
4663 max = (state.easyPieChartMax === null)?data.max:state.easyPieChartMax;
4664 pcent = NETDATA.percentFromValueMax(value, max);
4667 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4668 state.easyPieChart_instance.update(pcent);
4672 NETDATA.easypiechartChartCreate = function(state, data) {
4673 var self = $(state.element);
4674 var chart = $(state.element_chart);
4676 var value = data.result[0];
4677 var max = self.data('easypiechart-max-value') || null;
4678 var adjust = self.data('easypiechart-adjust') || null;
4682 state.easyPieChartMax = null;
4685 state.easyPieChartMax = max;
4687 var pcent = NETDATA.percentFromValueMax(value, max);
4689 chart.data('data-percent', pcent);
4693 case 'width': size = state.chartHeight(); break;
4694 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
4695 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
4697 default: size = state.chartWidth(); break;
4699 state.element.style.width = size + 'px';
4700 state.element.style.height = size + 'px';
4702 var stroke = Math.floor(size / 22);
4703 if(stroke < 3) stroke = 2;
4705 var valuefontsize = Math.floor((size * 2 / 3) / 5);
4706 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
4707 state.easyPieChartLabel = document.createElement('span');
4708 state.easyPieChartLabel.className = 'easyPieChartLabel';
4709 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
4710 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
4711 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
4712 state.element_chart.appendChild(state.easyPieChartLabel);
4714 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
4715 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
4716 state.easyPieChartTitle = document.createElement('span');
4717 state.easyPieChartTitle.className = 'easyPieChartTitle';
4718 state.easyPieChartTitle.innerHTML = state.title;
4719 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
4720 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
4721 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
4722 state.element_chart.appendChild(state.easyPieChartTitle);
4724 var unitfontsize = Math.round(titlefontsize * 0.9);
4725 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
4726 state.easyPieChartUnits = document.createElement('span');
4727 state.easyPieChartUnits.className = 'easyPieChartUnits';
4728 state.easyPieChartUnits.innerHTML = state.units;
4729 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
4730 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
4731 state.element_chart.appendChild(state.easyPieChartUnits);
4733 chart.easyPieChart({
4734 barColor: self.data('easypiechart-barcolor') || state.chartColors()[0], //'#ef1e25',
4735 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
4736 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
4737 scaleLength: self.data('easypiechart-scalelength') || 5,
4738 lineCap: self.data('easypiechart-linecap') || 'round',
4739 lineWidth: self.data('easypiechart-linewidth') || stroke,
4740 trackWidth: self.data('easypiechart-trackwidth') || undefined,
4741 size: self.data('easypiechart-size') || size,
4742 rotate: self.data('easypiechart-rotate') || 0,
4743 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
4744 easing: self.data('easypiechart-easing') || undefined
4747 // when we just re-create the chart
4748 // do not animate the first update
4750 if(typeof state.easyPieChart_instance !== 'undefined')
4753 state.easyPieChart_instance = chart.data('easyPieChart');
4754 if(animate === false) state.easyPieChart_instance.disableAnimation();
4755 state.easyPieChart_instance.update(pcent);
4756 if(animate === false) state.easyPieChart_instance.enableAnimation();
4760 // ----------------------------------------------------------------------------------------------------------------
4763 NETDATA.gaugeInitialize = function(callback) {
4764 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
4766 url: NETDATA.gauge_js,
4771 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
4774 NETDATA.chartLibraries.gauge.enabled = false;
4775 NETDATA.error(100, NETDATA.gauge_js);
4777 .always(function() {
4778 if(typeof callback === "function")
4783 NETDATA.chartLibraries.gauge.enabled = false;
4784 if(typeof callback === "function")
4789 NETDATA.gaugeAnimation = function(state, status) {
4792 if(typeof status === 'boolean' && status === false)
4794 else if(typeof status === 'number')
4797 state.gauge_instance.animationSpeed = speed;
4798 state.___gaugeOld__.speed = speed;
4801 NETDATA.gaugeSet = function(state, value, min, max) {
4802 if(typeof value !== 'number') value = 0;
4803 if(typeof min !== 'number') min = 0;
4804 if(typeof max !== 'number') max = 0;
4805 if(value > max) max = value;
4806 if(value < min) min = value;
4815 // gauge.js has an issue if the needle
4816 // is smaller than min or larger than max
4817 // when we set the new values
4818 // the needle will go crazy
4820 // to prevent it, we always feed it
4821 // with a percentage, so that the needle
4822 // is always between min and max
4823 var pcent = (value - min) * 100 / (max - min);
4825 // these should never happen
4826 if(pcent < 0) pcent = 0;
4827 if(pcent > 100) pcent = 100;
4829 state.gauge_instance.set(pcent);
4831 state.___gaugeOld__.value = value;
4832 state.___gaugeOld__.min = min;
4833 state.___gaugeOld__.max = max;
4836 NETDATA.gaugeSetLabels = function(state, value, min, max) {
4837 if(state.___gaugeOld__.valueLabel !== value) {
4838 state.___gaugeOld__.valueLabel = value;
4839 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
4841 if(state.___gaugeOld__.minLabel !== min) {
4842 state.___gaugeOld__.minLabel = min;
4843 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
4845 if(state.___gaugeOld__.maxLabel !== max) {
4846 state.___gaugeOld__.maxLabel = max;
4847 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
4851 NETDATA.gaugeClearSelection = function(state) {
4852 if(typeof state.gaugeEvent !== 'undefined') {
4853 if(state.gaugeEvent.timer !== null)
4854 clearTimeout(state.gaugeEvent.timer);
4856 state.gaugeEvent.timer = null;
4859 if(state.isAutoRefreshed() === true && state.data !== null) {
4860 NETDATA.gaugeChartUpdate(state, state.data);
4863 NETDATA.gaugeAnimation(state, false);
4864 NETDATA.gaugeSet(state, null, null, null);
4865 NETDATA.gaugeSetLabels(state, null, null, null);
4868 NETDATA.gaugeAnimation(state, true);
4872 NETDATA.gaugeSetSelection = function(state, t) {
4873 if(state.timeIsVisible(t) !== true)
4874 return NETDATA.gaugeClearSelection(state);
4876 var slot = state.calculateRowForTime(t);
4877 if(slot < 0 || slot >= state.data.result.length)
4878 return NETDATA.gaugeClearSelection(state);
4880 if(typeof state.gaugeEvent === 'undefined') {
4881 state.gaugeEvent = {
4889 var value = state.data.result[state.data.result.length - 1 - slot];
4890 var max = (state.gaugeMax === null)?state.data.max:state.gaugeMax;
4893 state.gaugeEvent.value = value;
4894 state.gaugeEvent.max = max;
4895 state.gaugeEvent.min = min;
4896 NETDATA.gaugeSetLabels(state, value, min, max);
4898 if(state.gaugeEvent.timer === null) {
4899 NETDATA.gaugeAnimation(state, false);
4901 state.gaugeEvent.timer = setTimeout(function() {
4902 state.gaugeEvent.timer = null;
4903 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
4904 }, NETDATA.options.current.charts_selection_animation_delay);
4910 NETDATA.gaugeChartUpdate = function(state, data) {
4911 var value, min, max;
4913 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
4917 NETDATA.gaugeSetLabels(state, null, null, null);
4920 value = data.result[0];
4922 max = (state.gaugeMax === null)?data.max:state.gaugeMax;
4923 if(value > max) max = value;
4924 NETDATA.gaugeSetLabels(state, value, min, max);
4927 NETDATA.gaugeSet(state, value, min, max);
4931 NETDATA.gaugeChartCreate = function(state, data) {
4932 var self = $(state.element);
4933 // var chart = $(state.element_chart);
4935 var value = data.result[0];
4936 var max = self.data('gauge-max-value') || null;
4937 var adjust = self.data('gauge-adjust') || null;
4938 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
4939 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
4940 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
4941 var stopColor = self.data('gauge-stop-color') || void 0;
4942 var generateGradient = self.data('gauge-generate-gradient') || false;
4946 state.gaugeMax = null;
4949 state.gaugeMax = max;
4951 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
4953 // case 'width': width = height * ratio; break;
4955 // default: height = width / ratio; break;
4957 //state.element.style.width = width.toString() + 'px';
4958 //state.element.style.height = height.toString() + 'px';
4963 lines: 12, // The number of lines to draw
4964 angle: 0.15, // The length of each line
4965 lineWidth: 0.44, // 0.44 The line thickness
4967 length: 0.8, // 0.9 The radius of the inner circle
4968 strokeWidth: 0.035, // The rotation offset
4969 color: pointerColor // Fill color
4971 colorStart: startColor, // Colors
4972 colorStop: stopColor, // just experiment with them
4973 strokeColor: strokeColor, // to see which ones work best for you
4975 generateGradient: generateGradient,
4979 if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
4980 options.percentColors = [
4981 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
4982 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
4983 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
4984 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
4985 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
4986 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
4987 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
4988 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
4989 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
4990 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
4991 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
4994 state.gauge_canvas = document.createElement('canvas');
4995 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
4996 state.gauge_canvas.className = 'gaugeChart';
4997 state.gauge_canvas.width = width;
4998 state.gauge_canvas.height = height;
4999 state.element_chart.appendChild(state.gauge_canvas);
5001 var valuefontsize = Math.floor(height / 6);
5002 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5003 state.gaugeChartLabel = document.createElement('span');
5004 state.gaugeChartLabel.className = 'gaugeChartLabel';
5005 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5006 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5007 state.element_chart.appendChild(state.gaugeChartLabel);
5009 var titlefontsize = Math.round(valuefontsize / 2);
5011 state.gaugeChartTitle = document.createElement('span');
5012 state.gaugeChartTitle.className = 'gaugeChartTitle';
5013 state.gaugeChartTitle.innerHTML = state.title;
5014 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5015 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5016 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5017 state.element_chart.appendChild(state.gaugeChartTitle);
5019 var unitfontsize = Math.round(titlefontsize * 0.9);
5020 state.gaugeChartUnits = document.createElement('span');
5021 state.gaugeChartUnits.className = 'gaugeChartUnits';
5022 state.gaugeChartUnits.innerHTML = state.units;
5023 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5024 state.element_chart.appendChild(state.gaugeChartUnits);
5026 state.gaugeChartMin = document.createElement('span');
5027 state.gaugeChartMin.className = 'gaugeChartMin';
5028 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5029 state.element_chart.appendChild(state.gaugeChartMin);
5031 state.gaugeChartMax = document.createElement('span');
5032 state.gaugeChartMax.className = 'gaugeChartMax';
5033 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5034 state.element_chart.appendChild(state.gaugeChartMax);
5036 // when we just re-create the chart
5037 // do not animate the first update
5039 if(typeof state.gauge_instance !== 'undefined')
5042 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5044 state.___gaugeOld__ = {
5053 // we will always feed a percentage
5054 state.gauge_instance.minValue = 0;
5055 state.gauge_instance.maxValue = 100;
5057 NETDATA.gaugeAnimation(state, animate);
5058 NETDATA.gaugeSet(state, value, 0, max);
5059 NETDATA.gaugeSetLabels(state, value, 0, max);
5060 NETDATA.gaugeAnimation(state, true);
5064 // ----------------------------------------------------------------------------------------------------------------
5065 // Charts Libraries Registration
5067 NETDATA.chartLibraries = {
5069 initialize: NETDATA.dygraphInitialize,
5070 create: NETDATA.dygraphChartCreate,
5071 update: NETDATA.dygraphChartUpdate,
5072 resize: function(state) {
5073 if(typeof state.dygraph_instance.resize === 'function')
5074 state.dygraph_instance.resize();
5076 setSelection: NETDATA.dygraphSetSelection,
5077 clearSelection: NETDATA.dygraphClearSelection,
5078 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5081 format: function(state) { return 'json'; },
5082 options: function(state) { return 'ms|flip'; },
5083 legend: function(state) {
5084 if(this.isSparkline(state) === false)
5085 return 'right-side';
5089 autoresize: function(state) { return true; },
5090 max_updates_to_recreate: function(state) { return 5000; },
5091 track_colors: function(state) { return true; },
5092 pixels_per_point: function(state) {
5093 if(this.isSparkline(state) === false)
5099 isSparkline: function(state) {
5100 if(typeof state.dygraph_sparkline === 'undefined') {
5101 var t = $(state.element).data('dygraph-theme');
5102 if(t === 'sparkline')
5103 state.dygraph_sparkline = true;
5105 state.dygraph_sparkline = false;
5107 return state.dygraph_sparkline;
5111 initialize: NETDATA.sparklineInitialize,
5112 create: NETDATA.sparklineChartCreate,
5113 update: NETDATA.sparklineChartUpdate,
5115 setSelection: undefined, // function(state, t) { return true; },
5116 clearSelection: undefined, // function(state) { return true; },
5117 toolboxPanAndZoom: null,
5120 format: function(state) { return 'array'; },
5121 options: function(state) { return 'flip|abs'; },
5122 legend: function(state) { return null; },
5123 autoresize: function(state) { return false; },
5124 max_updates_to_recreate: function(state) { return 5000; },
5125 track_colors: function(state) { return false; },
5126 pixels_per_point: function(state) { return 3; }
5129 initialize: NETDATA.peityInitialize,
5130 create: NETDATA.peityChartCreate,
5131 update: NETDATA.peityChartUpdate,
5133 setSelection: undefined, // function(state, t) { return true; },
5134 clearSelection: undefined, // function(state) { return true; },
5135 toolboxPanAndZoom: null,
5138 format: function(state) { return 'ssvcomma'; },
5139 options: function(state) { return 'null2zero|flip|abs'; },
5140 legend: function(state) { return null; },
5141 autoresize: function(state) { return false; },
5142 max_updates_to_recreate: function(state) { return 5000; },
5143 track_colors: function(state) { return false; },
5144 pixels_per_point: function(state) { return 3; }
5147 initialize: NETDATA.morrisInitialize,
5148 create: NETDATA.morrisChartCreate,
5149 update: NETDATA.morrisChartUpdate,
5151 setSelection: undefined, // function(state, t) { return true; },
5152 clearSelection: undefined, // function(state) { return true; },
5153 toolboxPanAndZoom: null,
5156 format: function(state) { return 'json'; },
5157 options: function(state) { return 'objectrows|ms'; },
5158 legend: function(state) { return null; },
5159 autoresize: function(state) { return false; },
5160 max_updates_to_recreate: function(state) { return 50; },
5161 track_colors: function(state) { return false; },
5162 pixels_per_point: function(state) { return 15; }
5165 initialize: NETDATA.googleInitialize,
5166 create: NETDATA.googleChartCreate,
5167 update: NETDATA.googleChartUpdate,
5169 setSelection: undefined, //function(state, t) { return true; },
5170 clearSelection: undefined, //function(state) { return true; },
5171 toolboxPanAndZoom: null,
5174 format: function(state) { return 'datatable'; },
5175 options: function(state) { return ''; },
5176 legend: function(state) { return null; },
5177 autoresize: function(state) { return false; },
5178 max_updates_to_recreate: function(state) { return 300; },
5179 track_colors: function(state) { return false; },
5180 pixels_per_point: function(state) { return 4; }
5183 initialize: NETDATA.raphaelInitialize,
5184 create: NETDATA.raphaelChartCreate,
5185 update: NETDATA.raphaelChartUpdate,
5187 setSelection: undefined, // function(state, t) { return true; },
5188 clearSelection: undefined, // function(state) { return true; },
5189 toolboxPanAndZoom: null,
5192 format: function(state) { return 'json'; },
5193 options: function(state) { return ''; },
5194 legend: function(state) { return null; },
5195 autoresize: function(state) { return false; },
5196 max_updates_to_recreate: function(state) { return 5000; },
5197 track_colors: function(state) { return false; },
5198 pixels_per_point: function(state) { return 3; }
5201 initialize: NETDATA.c3Initialize,
5202 create: NETDATA.c3ChartCreate,
5203 update: NETDATA.c3ChartUpdate,
5205 setSelection: undefined, // function(state, t) { return true; },
5206 clearSelection: undefined, // function(state) { return true; },
5207 toolboxPanAndZoom: null,
5210 format: function(state) { return 'csvjsonarray'; },
5211 options: function(state) { return 'milliseconds'; },
5212 legend: function(state) { return null; },
5213 autoresize: function(state) { return false; },
5214 max_updates_to_recreate: function(state) { return 5000; },
5215 track_colors: function(state) { return false; },
5216 pixels_per_point: function(state) { return 15; }
5219 initialize: NETDATA.d3Initialize,
5220 create: NETDATA.d3ChartCreate,
5221 update: NETDATA.d3ChartUpdate,
5223 setSelection: undefined, // function(state, t) { return true; },
5224 clearSelection: undefined, // function(state) { return true; },
5225 toolboxPanAndZoom: null,
5228 format: function(state) { return 'json'; },
5229 options: function(state) { return ''; },
5230 legend: function(state) { return null; },
5231 autoresize: function(state) { return false; },
5232 max_updates_to_recreate: function(state) { return 5000; },
5233 track_colors: function(state) { return false; },
5234 pixels_per_point: function(state) { return 3; }
5237 initialize: NETDATA.easypiechartInitialize,
5238 create: NETDATA.easypiechartChartCreate,
5239 update: NETDATA.easypiechartChartUpdate,
5241 setSelection: NETDATA.easypiechartSetSelection,
5242 clearSelection: NETDATA.easypiechartClearSelection,
5243 toolboxPanAndZoom: null,
5246 format: function(state) { return 'array'; },
5247 options: function(state) { return 'absolute'; },
5248 legend: function(state) { return null; },
5249 autoresize: function(state) { return false; },
5250 max_updates_to_recreate: function(state) { return 5000; },
5251 track_colors: function(state) { return true; },
5252 pixels_per_point: function(state) { return 3; },
5256 initialize: NETDATA.gaugeInitialize,
5257 create: NETDATA.gaugeChartCreate,
5258 update: NETDATA.gaugeChartUpdate,
5260 setSelection: NETDATA.gaugeSetSelection,
5261 clearSelection: NETDATA.gaugeClearSelection,
5262 toolboxPanAndZoom: null,
5265 format: function(state) { return 'array'; },
5266 options: function(state) { return 'absolute'; },
5267 legend: function(state) { return null; },
5268 autoresize: function(state) { return false; },
5269 max_updates_to_recreate: function(state) { return 5000; },
5270 track_colors: function(state) { return true; },
5271 pixels_per_point: function(state) { return 3; },
5276 NETDATA.registerChartLibrary = function(library, url) {
5277 if(NETDATA.options.debug.libraries === true)
5278 console.log("registering chart library: " + library);
5280 NETDATA.chartLibraries[library].url = url;
5281 NETDATA.chartLibraries[library].initialized = true;
5282 NETDATA.chartLibraries[library].enabled = true;
5285 // ----------------------------------------------------------------------------------------------------------------
5288 NETDATA.requiredJs = [
5290 url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
5291 isAlreadyLoaded: function() {
5292 if(typeof $().emulateTransitionEnd == 'function')
5295 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5303 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller.min.js',
5304 isAlreadyLoaded: function() { return false; }
5307 url: NETDATA.serverDefault + 'lib/bootstrap-toggle.min.js',
5308 isAlreadyLoaded: function() { return false; }
5312 NETDATA.requiredCSS = [
5314 url: NETDATA.themes.current.bootstrap_css,
5315 isAlreadyLoaded: function() {
5316 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5323 url: NETDATA.serverDefault + 'css/font-awesome.min.css',
5324 isAlreadyLoaded: function() { return false; }
5327 url: NETDATA.themes.current.dashboard_css,
5328 isAlreadyLoaded: function() { return false; }
5331 url: NETDATA.serverDefault + 'css/bootstrap-toggle.min.css',
5332 isAlreadyLoaded: function() { return false; }
5336 NETDATA.loadRequiredJs = function(index, callback) {
5337 if(index >= NETDATA.requiredJs.length) {
5338 if(typeof callback === 'function')
5343 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5344 NETDATA.loadRequiredJs(++index, callback);
5348 if(NETDATA.options.debug.main_loop === true)
5349 console.log('loading ' + NETDATA.requiredJs[index].url);
5352 url: NETDATA.requiredJs[index].url,
5356 .success(function() {
5357 if(NETDATA.options.debug.main_loop === true)
5358 console.log('loaded ' + NETDATA.requiredJs[index].url);
5360 NETDATA.loadRequiredJs(++index, callback);
5363 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5367 NETDATA.loadRequiredCSS = function(index) {
5368 if(index >= NETDATA.requiredCSS.length)
5371 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5372 NETDATA.loadRequiredCSS(++index);
5376 if(NETDATA.options.debug.main_loop === true)
5377 console.log('loading ' + NETDATA.requiredCSS[index].url);
5379 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5380 NETDATA.loadRequiredCSS(++index);
5383 NETDATA.errorReset();
5384 NETDATA.loadRequiredCSS(0);
5386 NETDATA._loadjQuery(function() {
5387 NETDATA.loadRequiredJs(0, function() {
5388 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
5389 if(NETDATA.options.debug.main_loop === true)
5390 console.log('starting chart refresh thread');
5397 // window.NETDATA = NETDATA;
5398 // })(window, document);