@cryptotaxi247 / netdata-1 / commits / 0191d6813

re-worked pan and zoom to allow data padding

Costa Tsaousis (ktsaou) committed Jan 15, 2016 at 00:43 UTC 0191d6813907a094958fbe44cef771d41d49ba8e
1 file changed +148 -84
web/dashboard.js
+148 -84
@@ -150,7 +150,7 @@
150 // rendering the chart that is panned or zoomed).
151 // Used with .current.global_pan_sync_time
152
153 - last_resized: 0, // the timestamp of the last resize request
153 + last_resized: new Date().getTime(), // the timestamp of the last resize request
154
155 crossDomainAjax: false, // enable this to request crossDomain AJAX
156
@@ -197,6 +197,8 @@
197
198 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
199
200 + pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
201 +
202 update_only_visible: true, // enable or disable visibility management
203
204 parallel_refresher: true, // enable parallel refresh of charts
@@ -574,8 +576,8 @@
576 if(this.master === null || this.seq === 0)
577 return false;
578
577 - if(state.needsRecreation())
578 - return true;
579 + //if(state.needsRecreation())
580 + // return true;
581
582 if(state.tm.pan_and_zoom_seq === this.seq)
583 return false;
@@ -916,6 +918,9 @@
918 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
919 this.requested_after = null; // milliseconds - the timestamp of the request after param
920 this.requested_before = null; // milliseconds - the timestamp of the request before param
921 + this.requested_padding = null;
922 + this.view_after = 0;
923 + this.view_before = 0;
924
925 this.auto = {
926 name: 'auto',
@@ -1022,8 +1027,11 @@
1027 }
1028 else {
1029 var w = that.element.offsetWidth;
1025 - if(w === null || w === 0)
1030 + if(w === null || w === 0) {
1031 + // the div is hidden
1032 + // this is resize the chart when next viewed
1033 that.tm.last_resized = 0;
1034 + }
1035 else
1036 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1037 }
@@ -1047,9 +1055,10 @@
1055 that.paused = false;
1056 that.selected = false;
1057
1050 - that.chart_created = false; // boolean - is the library.create() been called?
1051 - that.updates_counter = 0; // numeric - the number of refreshes made so far
1052 - that.updates_since_last_creation = 0;
1058 + that.chart_created = false; // boolean - is the library.create() been called?
1059 + that.updates_counter = 0; // numeric - the number of refreshes made so far
1060 + that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1061 + that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1062
1063 that.tm = {
1064 last_initialized: 0, // milliseconds - the timestamp it was last initialized
@@ -1177,6 +1186,7 @@
1186 showRendering();
1187 that.element_chart.style.display = 'none';
1188 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1189 + that.tm.last_hidden = new Date().getTime();
1190 }
1191 }
1192
@@ -1188,6 +1198,7 @@
1198 if(isHidden() === false) return;
1199
1200 that.___chartIsHidden___ = undefined;
1201 + that.updates_since_last_unhide = 0;
1202
1203 if(that.chart_created === false) {
1204 // we need to re-initialize it, to show our background
@@ -1195,6 +1206,7 @@
1206 init();
1207 }
1208 else {
1209 + that.tm.last_unhidden = new Date().getTime();
1210 that.element_chart.style.display = '';
1211 if(that.element_legend !== null) that.element_legend.style.display = '';
1212 resizeChart();
@@ -1232,7 +1244,6 @@
1244 return false;
1245 }
1246
1235 - that.updates_since_last_creation++;
1247 return true;
1248 }
1249
@@ -1305,7 +1316,6 @@
1316
1317 var now = new Date().getTime();
1318 NETDATA.options.last_page_scroll = now;
1308 - NETDATA.options.last_resized = now;
1319 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1320
1321 // force a resize
@@ -1413,7 +1423,10 @@
1423 showMessageIcon('<i class="fa fa-warning"></i> empty');
1424 that.legendUpdateDOM();
1425 that.tm.last_autorefreshed = new Date().getTime();
1416 - that.data_update_every = 30 * 1000;
1426 + // that.data_update_every = 30 * 1000;
1427 + //that.element_chart.style.display = 'none';
1428 + //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1429 + //that.___chartIsHidden___ = true;
1430 }
1431
1432 // ============================================================================================================
@@ -1708,45 +1721,76 @@
1721 }
1722
1723 this.updateChartPanOrZoom = function(after, before) {
1724 + var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
1725 + var ret = true;
1726 +
1727 if(this.debug === true)
1712 - this.log('updateChartPanOrZoom() called.');
1728 + this.log(logme);
1729
1714 - if(before < after) return false;
1730 + if(before < after) {
1731 + this.log(logme + 'flipped parameters, rejecting it.');
1732 + return false;
1733 + }
1734
1716 - var min_duration = Math.round((this.chartWidth() / 30 * this.chart.update_every * 1000));
1735 + if(typeof this.fixed_min_duration === 'undefined')
1736 + this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
1737 +
1738 + var min_duration = this.fixed_min_duration;
1739 + var current_duration = Math.round(this.view_before - this.view_after);
1740
1718 - if(this.debug === true)
1719 - this.log('requested duration of ' + ((before - after) / 1000).toString() + ' (' + after + ' - ' + before + '), minimum ' + min_duration / 1000);
1741 + // round the numbers
1742 + after = Math.round(after);
1743 + before = Math.round(before);
1744
1721 - if((before - after) < min_duration) return false;
1745 + // align them to update_every
1746 + // stretching them further away
1747 + after -= after % this.data_update_every;
1748 + before += this.data_update_every - (before % this.data_update_every);
1749
1723 - var current_duration = this.data_before - this.data_after;
1750 + // the final wanted duration
1751 var wanted_duration = before - after;
1725 - var tolerance = this.data_update_every * 2;
1726 - var movement = Math.abs(before - this.data_before);
1752 +
1753 + // to allow panning, accept just a point below our minimum
1754 + if((current_duration - this.data_update_every) < min_duration)
1755 + min_duration = current_duration - this.data_update_every;
1756 +
1757 + // we do it, but we adjust to minimum size and return false
1758 + // when the wanted size is below the current and the minimum
1759 + // and we zoom
1760 + if(this.current === this.zoom && wanted_duration < current_duration && wanted_duration < min_duration) {
1761 + if(this.debug === true)
1762 + this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
1763
1728 - if(this.debug === true)
1729 - this.log('current duration: ' + current_duration / 1000 + ', wanted duration: ' + wanted_duration / 1000 + ', movement: ' + movement / 1000 + ', tolerance: ' + tolerance / 1000);
1764 + min_duration = this.fixed_min_duration;
1765 +
1766 + var dt = (min_duration - wanted_duration) / 2;
1767 + before += dt;
1768 + after -= dt;
1769 + ret = false;
1770 + }
1771 +
1772 + var tolerance = this.data_update_every * 2;
1773 + var movement = Math.abs(before - this.view_before);
1774
1775 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance) {
1776 if(this.debug === true)
1733 - this.log('IGNORED');
1734 -
1777 + this.log(logme + 'REJECTING UPDATE: current duration: ' + (current_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);
1778 return false;
1779 }
1780
1781 if(this.current.name === 'auto') {
1782 + this.log(logme + 'caller called me with mode: ' + this.current.name);
1783 this.setMode('pan');
1740 -
1741 - if(this.debug === true)
1742 - this.log('updateChartPanOrZoom(): caller did not set proper mode');
1784 }
1785
1786 + if(this.debug === true)
1787 + this.log(logme + 'ACCEPTING UPDATE: current duration: ' + (current_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);
1788 +
1789 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
1790 this.current.force_after_ms = after;
1791 this.current.force_before_ms = before;
1792 NETDATA.globalPanAndZoom.setMaster(this, after, before);
1749 - return true;
1793 + return ret;
1794 }
1795
1796 this.legendFormatValue = function(value) {
@@ -1754,9 +1798,10 @@
1798 if(typeof value !== 'number') return value;
1799
1800 var abs = Math.abs(value);
1757 - if(abs >= 100) return Math.round(value).toLocaleString();
1758 - if(abs >= 1) return (Math.round(value * 100) / 100).toLocaleString();
1759 - if(abs >= 0.1) return (Math.round(value * 1000) / 1000).toLocaleString();
1801 + if(abs >= 1000) return (Math.round(value)).toLocaleString();
1802 + if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
1803 + if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
1804 + if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
1805 return (Math.round(value * 10000) / 10000).toLocaleString();
1806 }
1807
@@ -2182,17 +2227,47 @@
2227 }
2228
2229 this.chartURL = function() {
2185 - var before;
2186 - var after;
2187 - if(NETDATA.globalPanAndZoom.isActive()) {
2230 + var after, before, points_multiplier = 1;
2231 + if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2232 + this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2233 +
2234 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2235 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2190 - this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2236 + this.view_after = after * 1000;
2237 + this.view_before = before * 1000;
2238 +
2239 + this.requested_padding = null;
2240 + points_multiplier = 1;
2241 + }
2242 + else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2243 + this.tm.pan_and_zoom_seq = 0;
2244 +
2245 + before = Math.round(this.current.force_before_ms / 1000);
2246 + after = Math.round(this.current.force_after_ms / 1000);
2247 + this.view_after = after * 1000;
2248 + this.view_before = before * 1000;
2249 +
2250 + if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2251 + this.requested_padding = Math.round((before - after) / 2);
2252 + after -= this.requested_padding;
2253 + before += this.requested_padding;
2254 + this.requested_padding *= 1000;
2255 + points_multiplier = 2;
2256 + }
2257 +
2258 + this.current.force_before_ms = null;
2259 + this.current.force_after_ms = null;
2260 }
2261 else {
2193 - before = this.current.force_before_ms !== null ? Math.round(this.current.force_before_ms / 1000) : this.before;
2194 - after = this.current.force_after_ms !== null ? Math.round(this.current.force_after_ms / 1000) : this.after;
2262 this.tm.pan_and_zoom_seq = 0;
2263 +
2264 + before = this.before;
2265 + after = this.after;
2266 + this.view_after = after * 1000;
2267 + this.view_before = before * 1000;
2268 +
2269 + this.requested_padding = null;
2270 + points_multiplier = 1;
2271 }
2272
2273 this.requested_after = after * 1000;
@@ -2203,7 +2278,7 @@
2278 // build the data URL
2279 this.data_url = this.chart.data_url;
2280 this.data_url += "&format=" + this.library.format();
2206 - this.data_url += "&points=" + this.data_points.toString();
2281 + this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2282 this.data_url += "&group=" + this.method;
2283 this.data_url += "&options=" + this.library.options(this);
2284 this.data_url += '|jsonwrap';
@@ -2241,43 +2316,48 @@
2316
2317 this.data = data;
2318 this.updates_counter++;
2319 + this.updates_since_last_unhide++;
2320 + this.updates_since_last_creation++;
2321
2322 var started = new Date().getTime();
2323
2324 // if the result is JSON, find the latest update-every
2248 - if(typeof data === 'object') {
2249 - if(typeof data.view_update_every !== 'undefined')
2250 - this.data_update_every = data.view_update_every * 1000;
2251 -
2252 - if(typeof data.after !== 'undefined')
2253 - this.data_after = data.after * 1000;
2254 -
2255 - if(typeof data.before !== 'undefined')
2256 - this.data_before = data.before * 1000;
2257 -
2258 - if(typeof data.first_entry !== 'undefined')
2259 - this.netdata_first = data.first_entry * 1000;
2260 -
2261 - if(typeof data.last_entry !== 'undefined')
2262 - this.netdata_last = data.last_entry * 1000;
2263 -
2264 - if(typeof data.points !== 'undefined')
2265 - this.data_points = data.points;
2266 -
2267 - data.state = this;
2325 + this.data_update_every = data.view_update_every * 1000;
2326 + this.data_after = data.after * 1000;
2327 + this.data_before = data.before * 1000;
2328 + this.netdata_first = data.first_entry * 1000;
2329 + this.netdata_last = data.last_entry * 1000;
2330 + this.data_points = data.points;
2331 + data.state = this;
2332 +
2333 + if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2334 + if(this.view_after < this.data_after) {
2335 + console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2336 + this.view_after = this.data_after;
2337 + }
2338 +
2339 + if(this.view_before > this.data_before) {
2340 + console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2341 + this.view_before = this.data_before;
2342 + }
2343 + }
2344 + else {
2345 + this.view_after = this.data_after;
2346 + this.view_before = this.data_before;
2347 }
2348
2349 if(this.debug === true) {
2350 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2351
2352 if(this.current.force_after_ms)
2274 - this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2353 + this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2354 else
2276 - this.log('STATUS: forced: unset');
2355 + this.log('STATUS: forced : unset');
2356
2278 - this.log('STATUS: requested: ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2279 - this.log('STATUS: rendered : ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2280 - this.log('STATUS: points : ' + (this.data_points).toString());
2357 + this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2358 + this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2359 + this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2360 + this.log('STATUS: points : ' + (this.data_points).toString());
2361 }
2362
2363 if(this.data_points === 0) {
@@ -2410,12 +2490,8 @@
2490
2491 // caching - we do not evaluate the charts visibility
2492 // if the page has not been scrolled since the last check
2413 - if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll) {
2414 - if(this.debug === true)
2415 - this.log('isVisible: ' + this.___isVisible___);
2416 -
2493 + if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
2494 return this.___isVisible___;
2418 - }
2495
2496 this.tm.last_visible_check = new Date().getTime();
2497
@@ -2427,10 +2503,6 @@
2503 if(x.width === 0 || x.height === 0) {
2504 hideChart();
2505 this.___isVisible___ = false;
2430 -
2431 - if(this.debug === true)
2432 - this.log('isVisible (width:' + x.width + ', height:' + x.height + ', display:"' + this.element.style.display + '"): ' + this.___isVisible___);
2433 -
2506 return this.___isVisible___;
2507 }
2508
@@ -2448,10 +2520,6 @@
2520
2521 hideChart();
2522 this.___isVisible___ = false;
2451 -
2452 - if(this.debug === true)
2453 - this.log('isVisible: ' + this.___isVisible___);
2454 -
2523 return this.___isVisible___;
2524 }
2525 else {
@@ -2459,10 +2527,6 @@
2527
2528 unhideChart();
2529 this.___isVisible___ = true;
2462 -
2463 - if(this.debug === true)
2464 - this.log('isVisible: ' + this.___isVisible___);
2465 -
2530 return this.___isVisible___;
2531 }
2532 }
@@ -2506,7 +2570,7 @@
2570
2571 if(this.isAutoRefreshed() === true) {
2572 // allow the first update, even if the page is not visible
2509 - if(this.updates_counter && NETDATA.options.page_is_visible === false) {
2573 + if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
2574 if(NETDATA.options.debug.focus === true || this.debug === true)
2575 this.log('canBeAutoRefreshed(): page does not have focus');
2576
@@ -3269,7 +3333,7 @@
3333 var dygraph = state.dygraph_instance;
3334
3335 if(typeof dygraph === 'undefined')
3272 - NETDATA.dygraphChartCreate(state, data);
3336 + return NETDATA.dygraphChartCreate(state, data);
3337
3338 // when the chart is not visible, and hidden
3339 // if there is a window resize, dygraph detects
@@ -3287,7 +3351,7 @@
3351 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
3352 }
3353
3290 - if(state.current.name === 'pan') {
3354 + if(state.current.name !== 'auto') {
3355 if(NETDATA.options.debug.dygraph === true || state.debug === true)
3356 state.log('dygraphChartUpdate() loose update');
3357 }
@@ -3494,9 +3558,9 @@
3558 // here we calculate the time t based on the row number selected
3559 // which is ok
3560 var t = state.data_after + row * state.data_update_every;
3497 - // console.log('row = ' + row + ', x = ' + x + ', t = ' + t + ' ' + ((t === x)?'SAME':'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);
3561 + // 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);
3562
3499 - state.globalSelectionSync(t);
3563 + state.globalSelectionSync(x);
3564
3565 // fix legend zIndex using the internal structures of dygraph legend module
3566 // this works, but it is a hack!