new zoom functionality for dygraphs - now it happens first on the client and then on the server
Costa Tsaousis (ktsaou) committed
Dec 17, 2015 at 04:26 UTC
75086f1af9ecb45ed58f9ce3b8554d3d7a8b32d1
1 file changed
+123
-77
web/dashboard.js
+123
-77
@@ -602,7 +602,7 @@
602
chartState.prototype.globalSelectionSyncAbility = function() {
603
if(NETDATA.options.current.sync_selection === false)
604
return false;
605
-
605
+
606
if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime()) return false;
607
return true;
608
}
@@ -866,83 +866,43 @@
866
this.log('mode set to ' + this.current.name);
867
}
868
869
- chartState.prototype._minPanOrZoomStep = function() {
870
- return (((this.current.before_ms - this.current.after_ms) / this.current.points) * ((this.current.points * 5 / 100) + 1) );
871
- // return this.current.view_update_every * 10;
872
- }
869
+ chartState.prototype.updateChartPanOrZoom = function(after, before) {
870
+ if(before < after) return false;
871
874
- chartState.prototype._shouldBeMoved = function(old_after, old_before, new_after, new_before) {
875
- var dt_after = Math.abs(old_after - new_after);
876
- var dt_before = Math.abs(old_before - new_before);
877
- var old_range = old_before - old_after;
872
+ var min_duration = Math.round((this.chartWidth() / 30 * this.chart.update_every * 1000));
873
879
- var new_range = new_before - new_after;
880
- var dt = Math.abs(old_range - new_range);
881
- var step = Math.max(dt_after, dt_before, dt);
874
+ if(this.debug === true)
875
+ this.log('requested duration of ' + ((before - after) / 1000).toString() + ' (' + after + ' - ' + before + '), minimum ' + min_duration / 1000);
876
883
- var min_step = this._minPanOrZoomStep();
884
- if(new_range < old_range && new_range / this.chartWidth() < 100) {
885
- if(this.debug === true)
886
- this.log('_shouldBeMoved(' + (new_after / 1000).toString() + ' - ' + (new_before / 1000).toString() + '): minimum point size: 0.10, wanted point size: ' + (new_range / this.chartWidth() / 1000).toString() + ': TOO SMALL RANGE');
877
+ if((before - after) < min_duration) return false;
878
888
- return false;
889
- }
879
+ var current_duration = this.current.before_ms - this.current.after_ms;
880
+ var wanted_duration = before - after;
881
+ var tolerance = this.current.view_update_every * 2;
882
+ var movement = Math.abs(before - this.current.before_ms);
883
891
- if(step >= min_step) {
892
- if(this.debug === true)
893
- this.log('_shouldBeMoved(' + (new_after / 1000).toString() + ' - ' + (new_before / 1000).toString() + '): minimum step: ' + (min_step / 1000).toString() + ', this step: ' + (step / 1000).toString() + ': YES');
884
+ if(this.debug === true)
885
+ this.log('current duration: ' + current_duration / 1000 + ', wanted duration: ' + wanted_duration / 1000 + ', movement: ' + movement / 1000 + ', tolerance: ' + tolerance / 1000);
886
895
- return true;
896
- }
897
- else {
887
+ if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance) {
888
if(this.debug === true)
899
- this.log('_shouldBeMoved(' + (new_after / 1000).toString() + ' - ' + (new_before / 1000).toString() + '): minimum step: ' + (min_step / 1000).toString() + ', this step: ' + (step / 1000).toString() + ': NO');
889
+ this.log('IGNORED');
890
891
return false;
892
}
903
- }
904
-
905
- chartState.prototype.updateChartPanOrZoom = function(after, before) {
906
- var move = false;
893
894
if(this.current.name === 'auto') {
909
- if(this.debug === true)
910
- this.log('updateChartPanOrZoom(): caller did not set proper mode');
911
-
895
this.setMode('pan');
913
- }
914
-
915
- if(this.current.force_after_ms === null || this.current.force_before_ms === null) {
916
- if(this.debug === true)
917
- this.log('updateChartPanOrZoom(' + (after / 1000).toString() + ' - ' + (before / 1000).toString() + '): INIT');
896
919
- move = true;
920
- }
921
- else if(this._shouldBeMoved(this.current.force_after_ms, this.current.force_before_ms, after, before) && this._shouldBeMoved(this.current.after_ms, this.current.before_ms, after, before)) {
922
- if(this.debug === true)
923
- this.log('updateChartPanOrZoom(' + (after / 1000).toString() + ' - ' + (before / 1000).toString() + '): FORCE CHANGE from ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
924
-
925
- move = true;
926
- }
927
- else if(this._shouldBeMoved(this.current.requested_after_ms, this.current.requested_before_ms, after, before) && this._shouldBeMoved(this.current.after_ms, this.current.before_ms, after, before)) {
897
if(this.debug === true)
929
- this.log('updateChartPanOrZoom(' + (after / 1000).toString() + ' - ' + (before / 1000).toString() + '): REQUESTED CHANGE from ' + (this.current.requested_after_ms / 1000).toString() + ' - ' + (this.current.requested_before_ms / 1000).toString());
930
-
931
- move = true;
932
- }
933
-
934
- if(move) {
935
- this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
936
- this.current.force_after_ms = after;
937
- this.current.force_before_ms = before;
938
- NETDATA.globalPanAndZoom.setMaster(this, after, before);
939
- return true;
898
+ this.log('updateChartPanOrZoom(): caller did not set proper mode');
899
}
900
942
- if(this.debug === true)
943
- this.log('updateChartPanOrZoom(' + (after / 1000).toString() + ' - ' + (before / 1000).toString() + '): IGNORE');
944
-
945
- return false;
901
+ this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
902
+ this.current.force_after_ms = after;
903
+ this.current.force_before_ms = before;
904
+ NETDATA.globalPanAndZoom.setMaster(this, after, before);
905
+ return true;
906
}
907
908
chartState.prototype.legendFormatValue = function(value) {
@@ -1467,7 +1427,7 @@
1427
1428
this.log('STATUS: requested: ' + (this.current.requested_after_ms / 1000).toString() + ' - ' + (this.current.requested_before_ms / 1000).toString());
1429
this.log('STATUS: rendered : ' + (this.current.after_ms / 1000).toString() + ' - ' + (this.current.before_ms / 1000).toString());
1470
- this.log('STATUS: points : ' + (this.current.points).toString() + ', min step: ' + (this._minPanOrZoomStep() / 1000).toString());
1430
+ this.log('STATUS: points : ' + (this.current.points).toString());
1431
}
1432
1433
if(data.points === 0) {
@@ -2814,11 +2774,15 @@
2774
if(NETDATA.options.debug.dygraph === true)
2775
state.log('dygraphDrawCallback()');
2776
2777
+ var first = state.current.data.first_entry_t * 1000;
2778
+ var last = state.current.data.last_entry_t * 1000;
2779
+
2780
var x_range = dygraph.xAxisRange();
2781
var after = Math.round(x_range[0]);
2782
var before = Math.round(x_range[1]);
2783
2821
- state.updateChartPanOrZoom(after, before);
2784
+ if(before <= last && after >= first)
2785
+ state.updateChartPanOrZoom(after, before);
2786
}
2787
},
2788
zoomCallback: function(minDate, maxDate, yRanges) {
@@ -2827,7 +2791,14 @@
2791
2792
state.globalSelectionSyncStop();
2793
state.globalSelectionSyncDelay();
2830
- state.updateChartPanOrZoom(minDate, maxDate);
2794
+
2795
+ if(state.updateChartPanOrZoom(minDate, maxDate) == false) {
2796
+ // we should not zoom that much
2797
+ state.dygraph_instance.updateOptions({
2798
+ dateWindow: null,
2799
+ valueRange: null
2800
+ });
2801
+ }
2802
},
2803
highlightCallback: function(event, x, points, row, seriesName) {
2804
if(NETDATA.options.debug.dygraph === true || state.debug === true)
@@ -2928,7 +2899,8 @@
2899
click: function(event, dygraph, context) {
2900
if(NETDATA.options.debug.dygraph === true || state.debug === true)
2901
state.log('interactionModel.click()');
2931
- /*Dygraph.cancelEvent(event);*/
2902
+
2903
+ event.preventDefault();
2904
},
2905
dblclick: function(event, dygraph, context) {
2906
if(NETDATA.options.debug.dygraph === true || state.debug === true)
@@ -2942,30 +2914,104 @@
2914
if(NETDATA.options.debug.dygraph === true || state.debug === true)
2915
state.log('interactionModel.mousewheel()');
2916
2917
+ // Take the offset of a mouse event on the dygraph canvas and
2918
+ // convert it to a pair of percentages from the bottom left.
2919
+ // (Not top left, bottom is where the lower value is.)
2920
+ function offsetToPercentage(g, offsetX, offsetY) {
2921
+ // This is calculating the pixel offset of the leftmost date.
2922
+ var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
2923
+ var yar0 = g.yAxisRange(0);
2924
+
2925
+ // This is calculating the pixel of the higest value. (Top pixel)
2926
+ var yOffset = g.toDomCoords(null, yar0[1])[1];
2927
+
2928
+ // x y w and h are relative to the corner of the drawing area,
2929
+ // so that the upper corner of the drawing area is (0, 0).
2930
+ var x = offsetX - xOffset;
2931
+ var y = offsetY - yOffset;
2932
+
2933
+ // This is computing the rightmost pixel, effectively defining the
2934
+ // width.
2935
+ var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
2936
+
2937
+ // This is computing the lowest pixel, effectively defining the height.
2938
+ var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
2939
+
2940
+ // Percentage from the left.
2941
+ var xPct = w === 0 ? 0 : (x / w);
2942
+ // Percentage from the top.
2943
+ var yPct = h === 0 ? 0 : (y / h);
2944
+
2945
+ // The (1-) part below changes it from "% distance down from the top"
2946
+ // to "% distance up from the bottom".
2947
+ return [xPct, (1-yPct)];
2948
+ }
2949
+
2950
+ // Adjusts [x, y] toward each other by zoomInPercentage%
2951
+ // Split it so the left/bottom axis gets xBias/yBias of that change and
2952
+ // tight/top gets (1-xBias)/(1-yBias) of that change.
2953
+ //
2954
+ // If a bias is missing it splits it down the middle.
2955
+ function zoomRange(g, zoomInPercentage, xBias, yBias) {
2956
+ xBias = xBias || 0.5;
2957
+ yBias = yBias || 0.5;
2958
+
2959
+ function adjustAxis(axis, zoomInPercentage, bias) {
2960
+ var delta = axis[1] - axis[0];
2961
+ var increment = delta * zoomInPercentage;
2962
+ var foo = [increment * bias, increment * (1-bias)];
2963
+
2964
+ return [ axis[0] + foo[0], axis[1] - foo[1] ];
2965
+ }
2966
+
2967
+ var yAxes = g.yAxisRanges();
2968
+ var newYAxes = [];
2969
+ for (var i = 0; i < yAxes.length; i++) {
2970
+ newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
2971
+ }
2972
+
2973
+ return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
2974
+ }
2975
+
2976
if(event.altKey || event.shiftKey) {
2977
state.globalSelectionSyncStop();
2978
state.globalSelectionSyncDelay();
2979
2980
// http://dygraphs.com/gallery/interaction-api.js
2981
var normal = (event.detail) ? event.detail * -1 : event.wheelDelta / 40;
2951
- var percentage = normal / 10;
2982
+ var percentage = normal / 50;
2983
+
2984
+ if (!(event.offsetX && event.offsetY)){
2985
+ event.offsetX = event.layerX - event.target.offsetLeft;
2986
+ event.offsetY = event.layerY - event.target.offsetTop;
2987
+ }
2988
+
2989
+ var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
2990
+ var xPct = percentages[0];
2991
+ var yPct = percentages[1];
2992
2953
- var before_old = state.current.before_ms;
2954
- var after_old = state.current.after_ms;
2955
- var range_old = before_old - after_old;
2993
+ var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
2994
2957
- var range = range_old * ( 1 - percentage );
2958
- var dt = Math.round((range_old - range) / 2);
2995
+ var after = new_x_range[0];
2996
+ var before = new_x_range[1];
2997
2960
- var before = before_old - dt;
2961
- var after = after_old + dt;
2998
+ var first = (state.current.data.first_entry_t + state.current.data.view_update_every) * 1000;
2999
+ var last = (state.current.data.last_entry_t + state.current.data.view_update_every) * 1000;
3000
2963
- if(NETDATA.options.debug.dygraph === true)
2964
- state.log('percent: ' + percentage + ' from ' + after_old + ' - ' + before_old + ' to ' + after + ' - ' + before + ', range from ' + (before_old - after_old).toString() + ' to ' + (before - after).toString());
3001
+ if(before > last) {
3002
+ after -= (before - last);
3003
+ before = last;
3004
+ }
3005
+ if(after < first) {
3006
+ after = first;
3007
+ }
3008
3009
state.setMode('zoom');
2967
- state.updateChartPanOrZoom(after, before);
2968
- }
3010
+ if(state.updateChartPanOrZoom(after, before) === true)
3011
+ dygraph.updateOptions({ dateWindow: [ after, before ] });
3012
+
3013
+ event.preventDefault();
3014
+ }
3015
},
3016
touchstart: function(event, dygraph, context) {
3017
if(NETDATA.options.debug.dygraph === true || state.debug === true)