Skip to content

Commit edf5ffe

Browse files
phisihalfhp
authored andcommitted
PanZoom enhanced (halfhp#33)
* Introduce a new StepMode Why: Increment by value -> no good when zooming Increment by pixel -> no good when zooming subdivide -> depending on data chooses ticks at wired locations e.g (1.3 , 2.3, 3,3 instead of 1, 2, 3) Workaround: When you know your data: supply an array of predefined increments (by value) for StepModel to choose from to best fit the desired number of lines. For example: Start zoomed out with ticks every 100 and as you zoom in switch to 50,10,1 * Introduce a new StepMode Why: Increment by value -> no good when zooming Increment by pixel -> no good when zooming subdivide -> depending on data chooses ticks at wired locations e.g (1.3 , 2.3, 3,3 instead of 1, 2, 3) Workaround: When you know your data: supply an array of predefined increments (by value) for StepModel to choose from to best fit the desired number of lines. For example: Start zoomed out with ticks every 100 and as you zoom in switch to 50,10,1 * comments * Extended PanZoom New enum ZoomLimit to indicate in what way the zoom should be limited Checks: - Outer: do not zoom out beyond defined bounds - Min Space: if StepMode defines a increment by value do not zoom in beyond one visible grid line - None,Both.... * restore messed up build files * restore messed up build files * sanity check for StepModelFit.setSteps added unit test for StepModelFit * added test snap to minimum * getRangeStepMode() check removed * javadoc
1 parent bae8096 commit edf5ffe

3 files changed

Lines changed: 132 additions & 6 deletions

File tree

androidplot-core/src/main/java/com/androidplot/xy/PanZoom.java

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public class PanZoom implements View.OnTouchListener {
2525
private XYPlot plot;
2626
private Pan pan;
2727
private Zoom zoom;
28+
29+
private ZoomLimit zoomLimit;
2830
private boolean isEnabled = true;
2931

3032
private DragState dragState = DragState.NONE;
@@ -77,25 +79,71 @@ public enum Zoom {
7779
SCALE
7880
}
7981

82+
/**
83+
* Limits imposed on the zoom.
84+
*/
85+
public enum ZoomLimit {
86+
/**
87+
* Do not zoom outside the plots outer bounds, if they are defined.
88+
*/
89+
OUTER,
90+
91+
/**
92+
* Additionally to the outer bounds if plot.StepModel defines a value based increment
93+
* make sure at least one tick is visible by not zooming in further.
94+
*/
95+
MIN_TICKS
96+
}
97+
8098
protected PanZoom(XYPlot plot, Pan pan, Zoom zoom) {
8199
this.plot = plot;
82100
this.pan = pan;
83101
this.zoom = zoom;
102+
this.zoomLimit = ZoomLimit.OUTER;
103+
}
104+
105+
// additional constructor not to break api
106+
protected PanZoom(XYPlot plot, Pan pan, Zoom zoom, ZoomLimit limit) {
107+
this.plot = plot;
108+
this.pan = pan;
109+
this.zoom = zoom;
110+
this.zoomLimit = limit;
84111
}
85112

86113
/**
87114
* Convenience method for enabling pan/zoom behavior on an instance of {@link XYPlot}, using
88115
* a default behavior of {@link Pan#BOTH} and {@link Zoom#SCALE}.
89-
* Use {@link PanZoom#attach(XYPlot, Pan, Zoom)} for finer grain control of this behavior.
116+
* Use {@link PanZoom#attach(XYPlot, Pan, Zoom, ZoomLimit)} for finer grain control of this behavior.
90117
* @param plot
91118
* @return
92119
*/
93120
public static PanZoom attach(XYPlot plot) {
94121
return attach(plot, Pan.BOTH, Zoom.SCALE);
95122
}
96123

124+
/**
125+
* Old method for enabling pan/zoom behavior on an instance of {@link XYPlot}, using
126+
* the default behavior of {@link ZoomLimit#OUTER}.
127+
* Use {@link PanZoom#attach(XYPlot, Pan, Zoom, ZoomLimit)} for finer grain control of this behavior.
128+
* @param plot
129+
* @param pan
130+
* @param zoom
131+
* @return
132+
*/
97133
public static PanZoom attach(XYPlot plot, Pan pan, Zoom zoom) {
98-
PanZoom pz = new PanZoom(plot, pan, zoom);
134+
return attach(plot,pan,zoom, ZoomLimit.OUTER);
135+
}
136+
137+
/**
138+
* New method for enabling pan/zoom behavior on an instance of {@link XYPlot}.
139+
* @param plot
140+
* @param pan
141+
* @param zoom
142+
* @param limit
143+
* @return
144+
*/
145+
public static PanZoom attach(XYPlot plot, Pan pan, Zoom zoom, ZoomLimit limit) {
146+
PanZoom pz = new PanZoom(plot, pan, zoom, limit);
99147
plot.setOnTouchListener(pz);
100148
return pz;
101149
}
@@ -332,10 +380,18 @@ protected void calculateZoom(RectF newRect, float scale, boolean isHorizontal) {
332380
}
333381

334382
final float midPoint = calcMax - (span / 2.0f);
335-
final float offset = span * scale / 2.0f;
383+
float offset = span * scale / 2.0f;
384+
final RectRegion limits = plot.getOuterLimits();
336385

337386
if (isHorizontal ) {
338-
final RectRegion limits = plot.getOuterLimits();
387+
// zoom limited and increment by value StepMode?
388+
if (zoomLimit == ZoomLimit.MIN_TICKS) {
389+
// make sure we do not zoom in too far (there should be at least one grid line visible)
390+
if (plot.getDomainStepValue() > (scale*span)) {
391+
offset = (float)(plot.getDomainStepValue() / 2.0f);
392+
}
393+
}
394+
339395
newRect.left = midPoint - offset;
340396
newRect.right = midPoint + offset;
341397
if(limits.isFullyDefined()) {
@@ -347,7 +403,14 @@ protected void calculateZoom(RectF newRect, float scale, boolean isHorizontal) {
347403
}
348404
}
349405
} else {
350-
final RectRegion limits = plot.getOuterLimits();
406+
// zoom limited and increment by value StepMode?
407+
if (zoomLimit == ZoomLimit.MIN_TICKS) {
408+
// make sure we do not zoom in too far (there should be at least one grid line visible)
409+
if (plot.getRangeStepValue() > (scale*span)) {
410+
offset = (float)(plot.getRangeStepValue() / 2.0f);
411+
}
412+
}
413+
351414
newRect.top = midPoint - offset;
352415
newRect.bottom = midPoint + offset;
353416
if(limits.isFullyDefined()) {
@@ -377,6 +440,14 @@ public void setZoom(Zoom zoom) {
377440
this.zoom = zoom;
378441
}
379442

443+
public ZoomLimit getZoomLimit() {
444+
return zoomLimit;
445+
}
446+
447+
public void setZoomLimit(ZoomLimit zoomLimit) {
448+
this.zoomLimit = zoomLimit;
449+
}
450+
380451
public View.OnTouchListener getDelegate() {
381452
return delegate;
382453
}

androidplot-core/src/test/java/com/androidplot/xy/PanZoomTest.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,61 @@ public void testZoom() {
178178

179179
}
180180

181+
@Test
182+
public void testLimitZoom() {
183+
double[] inc_domain = new double[]{10,50,100};
184+
double[] inc_range = new double[]{20,50};
185+
186+
xyPlot = spy(new InstrumentedXYPlot(getContext()));
187+
xyPlot.setDomainBoundaries(0, 20, BoundaryMode.FIXED);
188+
xyPlot.setRangeBoundaries(0, 30, BoundaryMode.FIXED);
189+
xyPlot.setDomainStepModel(new StepModelFit(xyPlot.getBounds().getxRegion(), inc_domain, 5));
190+
xyPlot.setRangeStepModel(new StepModelFit(xyPlot.getBounds().getyRegion(), inc_range, 5));
191+
xyPlot.redraw();
192+
193+
PanZoom panZoom = spy(new PanZoom(xyPlot, PanZoom.Pan.BOTH, PanZoom.Zoom.SCALE, PanZoom.ZoomLimit.MIN_TICKS));
194+
195+
// cap our pan/zoom boundaries:
196+
xyPlot.getOuterLimits().set(0, 20, 0, 30);
197+
198+
panZoom.setFingersRect(new RectF(0, 0, 20, 20));
199+
200+
InOrder inOrder = inOrder(xyPlot);
201+
inOrder.verify(xyPlot).setDomainBoundaries(0, 20, BoundaryMode.FIXED);
202+
203+
// should NOT result in a 2x zoom on domain centerpoint, but in a zoom to
204+
// the minimum spacing 10 and 20 respectively
205+
panZoom.zoom(TestUtils.newPointerDownEvent(0, 0, 40, 40));
206+
inOrder.verify(xyPlot).setDomainBoundaries(5f, 15f, BoundaryMode.FIXED);
207+
inOrder.verify(xyPlot).setRangeBoundaries(5f, 25f, BoundaryMode.FIXED);
208+
inOrder.verify(xyPlot).redraw();
209+
210+
// to zoom in beyond min limits
211+
panZoom.setZoomLimit(PanZoom.ZoomLimit.OUTER);
212+
213+
// should result in another 2x zoom on domain centerpoint:
214+
panZoom.zoom(TestUtils.newPointerDownEvent(0, 0, 80, 80));
215+
inOrder.verify(xyPlot).setDomainBoundaries(7.5f, 12.5f, BoundaryMode.FIXED);
216+
inOrder.verify(xyPlot).setRangeBoundaries(10f, 20f, BoundaryMode.FIXED);
217+
inOrder.verify(xyPlot).redraw();
218+
219+
// back to limited zoom
220+
panZoom.setZoomLimit(PanZoom.ZoomLimit.MIN_TICKS);
221+
222+
// try to zoom in further, should snap back to min limit:
223+
panZoom.zoom(TestUtils.newPointerDownEvent(0, 0, 90, 90));
224+
inOrder.verify(xyPlot).setDomainBoundaries(5f, 15f, BoundaryMode.FIXED);
225+
inOrder.verify(xyPlot).setRangeBoundaries(5f, 25f, BoundaryMode.FIXED);
226+
inOrder.verify(xyPlot).redraw();
227+
228+
// redraw should not be called again
229+
inOrder.verify(xyPlot, never()).redraw();
230+
231+
// make sure no panning took place during these zoom ops:
232+
verify(panZoom, never()).pan(any(MotionEvent.class));
233+
234+
}
235+
181236
@Test
182237
public void testFingerDistance() {
183238
PanZoom panZoom = spy(new PanZoom(xyPlot, PanZoom.Pan.BOTH, PanZoom.Zoom.SCALE));

demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public void onClick(View view) {
7878

7979
plot.setBorderStyle(Plot.BorderStyle.NONE, null, null);
8080

81-
panZoom = PanZoom.attach(plot);
81+
panZoom = PanZoom.attach(plot, PanZoom.Pan.BOTH, PanZoom.Zoom.STRETCH_BOTH, PanZoom.ZoomLimit.MIN_TICKS);
8282
plot.getOuterLimits().set(0, 3000, 0, 1000);
8383
initSpinners();
8484

0 commit comments

Comments
 (0)