@cryptotaxi247 / netdata-1 / commits / 75086f1af

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)