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!