From bae80969de40605d3e5432aaf8119ba729d4c7a9 Mon Sep 17 00:00:00 2001 From: phisi Date: Sat, 25 Feb 2017 15:04:05 +0100 Subject: [PATCH] Introduce a new StepMode (#32) * 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 * restore messed up build files * sanity check for StepModelFit.setSteps added unit test for StepModelFit --- .../java/com/androidplot/xy/StepMode.java | 4 +- .../java/com/androidplot/xy/StepModelFit.java | 85 +++++++++++++++++++ .../com/androidplot/xy/XYGraphWidget.java | 2 +- .../com/androidplot/xy/XYStepCalculator.java | 1 + .../com/androidplot/xy/StepModelFitTest.java | 61 +++++++++++++ .../demos/TouchZoomExampleActivity.java | 11 ++- 6 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 androidplot-core/src/main/java/com/androidplot/xy/StepModelFit.java create mode 100644 androidplot-core/src/test/java/com/androidplot/xy/StepModelFitTest.java diff --git a/androidplot-core/src/main/java/com/androidplot/xy/StepMode.java b/androidplot-core/src/main/java/com/androidplot/xy/StepMode.java index d5db2c9d..00f60747 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/StepMode.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/StepMode.java @@ -20,9 +20,11 @@ * INCREMENTAL_VALUE - (default) draw a tick every n values. * INCREMENTAL_PIXEL - draw a tick every n pixels. * SUBDIVIDE - draw n number of evenly spaced lines. + * INCREMENT_BY_FIT choose increment from a list of possible values */ public enum StepMode { SUBDIVIDE, // default INCREMENT_BY_VAL, - INCREMENT_BY_PIXELS + INCREMENT_BY_PIXELS, + INCREMENT_BY_FIT } diff --git a/androidplot-core/src/main/java/com/androidplot/xy/StepModelFit.java b/androidplot-core/src/main/java/com/androidplot/xy/StepModelFit.java new file mode 100644 index 00000000..d2b12f0b --- /dev/null +++ b/androidplot-core/src/main/java/com/androidplot/xy/StepModelFit.java @@ -0,0 +1,85 @@ +package com.androidplot.xy; + +import com.androidplot.Region; + +import java.util.Arrays; + +/** + * Subclass of StepModel that chooses from predefined step values. Depending on the currently + * displayed range (by value) choose increment so that the number of lines + * is closest to StepModel.value + */ +public class StepModelFit extends StepModel { + + private double[] steps; // list of steps to choose from + private Region scale; // axis region on display + + public StepModelFit(Region axisRegion, double[] increments, double numLines) { + super(StepMode.INCREMENT_BY_FIT, numLines); + + setSteps(increments); + setScale(axisRegion); + } + + public double[] getSteps() { + return steps; + } + + public void setSteps(double[] steps) { + + // sanity checks: no null, 0 or negative + if (steps == null || steps.length == 0) + return; + + for (double step : steps) { + if (step <= 0.0d) + return; + } + + this.steps = steps; + } + + public Region getScale() { + return scale; + } + + public void setScale(Region scale) { + this.scale = scale; + } + + // does not return StepModel.value instead calculates best fit + @Override + public double getValue() { + + // no possible increments where supplied + // or no region defined + if (steps == null || scale == null || !scale.isDefined()) + return super.getValue(); + + double curStep = steps[0]; + double oldDistance = Math.abs((scale.length().doubleValue() / curStep)-super.getValue() ); + + // determine which step size comes closest to the desired number of steps + // since steps[] is a small array brute force search is ok + for (double step : steps) { + + double newDistance = Math.abs((scale.length().doubleValue() / step)-super.getValue() ); + + // closer than previous stepping? + if (newDistance < oldDistance){ + curStep = step; + oldDistance = newDistance; + } + } + return curStep; + } + + @Override + public String toString() { + return "StepModelFit{" + + "steps=" + Arrays.toString(steps) + + ", scale=" + scale + + ", current stepping=" + getValue() + + '}'; + } +} diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java b/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java index 7f9114b4..f0f6ef38 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java @@ -546,7 +546,7 @@ protected void drawLineLabel(Canvas canvas, Edge edge, Number val, float x, floa } /** - * Draws the drid and domain/range labels for the plot. + * Draws the grid and domain/range labels for the plot. * * @param canvas */ diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYStepCalculator.java b/androidplot-core/src/main/java/com/androidplot/xy/XYStepCalculator.java index d90d9172..34de518d 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYStepCalculator.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYStepCalculator.java @@ -59,6 +59,7 @@ public static Step getStep(StepMode typeXY, double stepValue, Region realBounds, double stepCount = 0; switch(typeXY) { case INCREMENT_BY_VAL: + case INCREMENT_BY_FIT: stepVal = stepValue; stepPix = stepValue / realBounds.ratio(pixelBounds).doubleValue(); stepCount = pixelBounds.length().doubleValue() / stepPix; diff --git a/androidplot-core/src/test/java/com/androidplot/xy/StepModelFitTest.java b/androidplot-core/src/test/java/com/androidplot/xy/StepModelFitTest.java new file mode 100644 index 00000000..5f34fd52 --- /dev/null +++ b/androidplot-core/src/test/java/com/androidplot/xy/StepModelFitTest.java @@ -0,0 +1,61 @@ +package com.androidplot.xy; + +import com.androidplot.Region; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class StepModelFitTest { + + Region regionSmall = new Region(0,11); + Region regionBig = new Region(-111,420); + Region regionZero = new Region(0, 0); + Region regionUndef = new Region(0, null); + + double[] stpSmall = {1,2,5}, stpBig = {1,10,100}, nonsense = {0}; + + @Before + public void setUp() throws Exception { + + } + + @After + public void tearDown() throws Exception { + + } + + @Test + public void getValue() throws Exception { + + StepModelFit model = new StepModelFit(regionSmall,stpSmall,3); + + assertEquals(5.0, model.getValue(), 0.0); + model.setValue(5.0); + assertEquals(2.0, model.getValue(), 0.0); + model.setValue(7.0); + assertEquals(2.0, model.getValue(), 0.0); + + model.setSteps(stpBig); + assertEquals(1.0, model.getValue(), 0.0); + + model.setScale(regionBig); + assertEquals(100.0, model.getValue(), 0.0); + model.setValue(1000.0); + assertEquals(1.0, model.getValue(), 0.0); + + // bad parameters + model.setSteps(nonsense); + assertArrayEquals(stpBig,model.getSteps(), 0.0); + + model.setScale(regionZero); + assertEquals(stpBig[0], model.getValue(), 0.0); + + model.setScale(regionUndef); + model.setValue(1.1); + assertEquals(1.1, model.getValue(), 0.0); + } + +} \ No newline at end of file diff --git a/demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java b/demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java index ebf493d3..6d7cd9e0 100644 --- a/demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java @@ -31,6 +31,7 @@ public class TouchZoomExampleActivity extends Activity { private static final int SERIES_SIZE = 3000; private static final int SERIES_ALPHA = 255; + private static final int NUM_GRIDLINES = 5; private XYPlot plot; private PanZoom panZoom; private Button resetButton; @@ -53,8 +54,14 @@ public void onClick(View view) { // move dynamically with the data when the users pans or zooms: plot.setUserDomainOrigin(0); plot.setUserRangeOrigin(0); - plot.setDomainStep(StepMode.INCREMENT_BY_VAL, 500); - plot.setRangeStep(StepMode.INCREMENT_BY_VAL, 100); + + // predefine the stepping of both axis + // increment will be chosen from list to best fit NUM_GRIDLINES grid lines + double[] inc_domain = new double[]{10,50,100,500}; + double[] inc_range = new double[]{1,5,10,20,50,100}; + plot.setDomainStepModel(new StepModelFit(plot.getBounds().getxRegion(),inc_domain,NUM_GRIDLINES)); + plot.setRangeStepModel( new StepModelFit(plot.getBounds().getyRegion(),inc_range,NUM_GRIDLINES)); + panSpinner = (Spinner) findViewById(R.id.pan_spinner); zoomSpinner = (Spinner) findViewById(R.id.zoom_spinner);