From 29a30ccb779a870867132684f1b6ce77450cbb39 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Wed, 4 Jan 2017 14:41:05 -0600 Subject: [PATCH 01/81] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d25eece..b07eb9bd 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A library for creating dynamic and static charts in Android apps. It’s designe compatible with all versions of Android from 1.6 onward and is **used by over [1,000 apps](http://www.appbrain.com/stats/libraries/details/androidplot/androidplot) on Google Play**. -[![Codix](http://codix.io/badge/halfhp/androidplot)](http://codix.io/repo/halfhp/androidplot) +[![Codix](http://codix.io/gh/badge/halfhp/androidplot)](http://codix.io/gh/repo/halfhp/androidplot) If you enjoy the lib, please [rate us on codix.io](http://codix.io/repo/halfhp/androidplot)! From 40baa0ed1cd1b0632d41099fa068df232fb9c7a5 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Thu, 5 Jan 2017 08:18:34 -0600 Subject: [PATCH 02/81] #26 Fixed an NPE issue when drawing null values with a PointLabeler. --- .../androidplot/xy/LineAndPointRenderer.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/androidplot-core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java b/androidplot-core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java index bbe145d4..959cd784 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java @@ -246,16 +246,18 @@ protected void renderPoints(Canvas canvas, RectF plotArea, XYSeries series, int final PointLabeler pointLabeler = hasPointLabelFormatter ? formatter.getPointLabeler() : null; for(int i = iStart; i < iEnd; i++) { PointF p = points.get(i); + if(p != null) { - // if vertexPaint is available, draw vertex: - if (vertexPaint != null) { - canvas.drawPoint(p.x, p.y, vertexPaint); - } + // if vertexPaint is available, draw vertex: + if (vertexPaint != null) { + canvas.drawPoint(p.x, p.y, vertexPaint); + } - // if textPaint and pointLabeler are available, draw point's text label: - if (pointLabeler != null) { - canvas.drawText(pointLabeler.getLabel(series, i), - p.x + plf.hOffset, p.y + plf.vOffset, plf.getTextPaint()); + // if textPaint and pointLabeler are available, draw point's text label: + if (pointLabeler != null) { + canvas.drawText(pointLabeler.getLabel(series, i), + p.x + plf.hOffset, p.y + plf.vOffset, plf.getTextPaint()); + } } } } From 2d3c217ea8954f23edfc4ce6821ab8ba8bb327b7 Mon Sep 17 00:00:00 2001 From: Tim Hepner Date: Sat, 7 Jan 2017 05:38:59 -0800 Subject: [PATCH 03/81] fix broken link in quickstart (#27) --- docs/quickstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quickstart.md b/docs/quickstart.md index 7798928e..3f9b6833 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -45,7 +45,7 @@ and add an XYPlot view: ``` This example uses a default style to decorate the plot. The full list of styleable attributes is -[available here](../androidplot-core/src/main/res/attrs.xml). While new attributes are added regularly, +[available here](../androidplot-core/src/main/res/values/attrs.xml). While new attributes are added regularly, not all configurable properties are yet available. If something you need is missing, use [Fig Syntax](https://github.com/halfhp/fig) From cd7cbb89e6efc17b35d41206d54e8837b0172869 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Sun, 15 Jan 2017 08:25:23 -0600 Subject: [PATCH 04/81] updates prepping for 1.4.1 release --- docs/quickstart.md | 2 +- docs/release_notes.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/quickstart.md b/docs/quickstart.md index 3f9b6833..ce7e5426 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -14,7 +14,7 @@ To use the library in your gradle project add the following to your build.gradle ```groovy dependencies { - compile "com.androidplot:androidplot-core:1.4.0" + compile "com.androidplot:androidplot-core:1.4.1" } ``` diff --git a/docs/release_notes.md b/docs/release_notes.md index e29020a7..a593416e 100644 --- a/docs/release_notes.md +++ b/docs/release_notes.md @@ -2,6 +2,10 @@ For details on what to expect in general when updating to a new version of Androiplot, check out the [versioning doc](versioning.md). +# 1.4.1 +* (#26) Fixed an NPE issue when drawing null values with a PointLabeler. +* Fixed a broken link in Quickstart doc. + # 1.4.0 * Moderate refactor of `PieRenderer`. [Documentation](piechart.md) has been updated to reflect these changes. From e95dbfd73f56026a50c7748c5e903b5a7e908e8e Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Tue, 7 Feb 2017 12:27:50 -0600 Subject: [PATCH 05/81] Set theme jekyll-theme-minimal --- _config.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 _config.yml diff --git a/_config.yml b/_config.yml new file mode 100644 index 00000000..2f7efbea --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-minimal \ No newline at end of file From 84e4fa2854b2a7fb5b152f35b3e4b56da1abb4d8 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Tue, 7 Feb 2017 12:46:39 -0600 Subject: [PATCH 06/81] uprev for builds --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 196377de..51463dd9 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ ext { theCompileSdkVersion = 24 theTargetSdkVersion = 24 theMinSdkVersion = 5 - theVersionName = '1.4.1' + theVersionName = '1.4.2' theVersionCode = 0 } From 8e5fcc158e102892ab5d4cdbb3592756ba5129d8 Mon Sep 17 00:00:00 2001 From: Michael Rivera Date: Sun, 12 Feb 2017 12:54:06 -0500 Subject: [PATCH 07/81] Fix crash when clearing SimpleXYSeries with ArrayFormat SimpleXYSeries.ArrayFormat.Y_VALS_ONLY (#31) --- .../src/main/java/com/androidplot/xy/SimpleXYSeries.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/androidplot-core/src/main/java/com/androidplot/xy/SimpleXYSeries.java b/androidplot-core/src/main/java/com/androidplot/xy/SimpleXYSeries.java index f5ed5458..f346694a 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/SimpleXYSeries.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/SimpleXYSeries.java @@ -341,7 +341,9 @@ public LinkedList getyVals() { public void clear() { lock.writeLock().lock(); try { - xVals.clear(); + if (xVals != null) { + xVals.clear(); + } yVals.clear(); } finally { lock.writeLock().unlock(); From bae80969de40605d3e5432aaf8119ba729d4c7a9 Mon Sep 17 00:00:00 2001 From: phisi Date: Sat, 25 Feb 2017 15:04:05 +0100 Subject: [PATCH 08/81] 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); From edf5ffe8d0b9cde355350e595b1456170de5f8bd Mon Sep 17 00:00:00 2001 From: phisi Date: Wed, 1 Mar 2017 16:08:29 +0100 Subject: [PATCH 09/81] PanZoom enhanced (#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 --- .../main/java/com/androidplot/xy/PanZoom.java | 81 +++++++++++++++++-- .../java/com/androidplot/xy/PanZoomTest.java | 55 +++++++++++++ .../demos/TouchZoomExampleActivity.java | 2 +- 3 files changed, 132 insertions(+), 6 deletions(-) diff --git a/androidplot-core/src/main/java/com/androidplot/xy/PanZoom.java b/androidplot-core/src/main/java/com/androidplot/xy/PanZoom.java index 00faa739..a8793264 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/PanZoom.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/PanZoom.java @@ -25,6 +25,8 @@ public class PanZoom implements View.OnTouchListener { private XYPlot plot; private Pan pan; private Zoom zoom; + + private ZoomLimit zoomLimit; private boolean isEnabled = true; private DragState dragState = DragState.NONE; @@ -77,16 +79,41 @@ public enum Zoom { SCALE } + /** + * Limits imposed on the zoom. + */ + public enum ZoomLimit { + /** + * Do not zoom outside the plots outer bounds, if they are defined. + */ + OUTER, + + /** + * Additionally to the outer bounds if plot.StepModel defines a value based increment + * make sure at least one tick is visible by not zooming in further. + */ + MIN_TICKS + } + protected PanZoom(XYPlot plot, Pan pan, Zoom zoom) { this.plot = plot; this.pan = pan; this.zoom = zoom; + this.zoomLimit = ZoomLimit.OUTER; + } + + // additional constructor not to break api + protected PanZoom(XYPlot plot, Pan pan, Zoom zoom, ZoomLimit limit) { + this.plot = plot; + this.pan = pan; + this.zoom = zoom; + this.zoomLimit = limit; } /** * Convenience method for enabling pan/zoom behavior on an instance of {@link XYPlot}, using * a default behavior of {@link Pan#BOTH} and {@link Zoom#SCALE}. - * Use {@link PanZoom#attach(XYPlot, Pan, Zoom)} for finer grain control of this behavior. + * Use {@link PanZoom#attach(XYPlot, Pan, Zoom, ZoomLimit)} for finer grain control of this behavior. * @param plot * @return */ @@ -94,8 +121,29 @@ public static PanZoom attach(XYPlot plot) { return attach(plot, Pan.BOTH, Zoom.SCALE); } + /** + * Old method for enabling pan/zoom behavior on an instance of {@link XYPlot}, using + * the default behavior of {@link ZoomLimit#OUTER}. + * Use {@link PanZoom#attach(XYPlot, Pan, Zoom, ZoomLimit)} for finer grain control of this behavior. + * @param plot + * @param pan + * @param zoom + * @return + */ public static PanZoom attach(XYPlot plot, Pan pan, Zoom zoom) { - PanZoom pz = new PanZoom(plot, pan, zoom); + return attach(plot,pan,zoom, ZoomLimit.OUTER); + } + + /** + * New method for enabling pan/zoom behavior on an instance of {@link XYPlot}. + * @param plot + * @param pan + * @param zoom + * @param limit + * @return + */ + public static PanZoom attach(XYPlot plot, Pan pan, Zoom zoom, ZoomLimit limit) { + PanZoom pz = new PanZoom(plot, pan, zoom, limit); plot.setOnTouchListener(pz); return pz; } @@ -332,10 +380,18 @@ protected void calculateZoom(RectF newRect, float scale, boolean isHorizontal) { } final float midPoint = calcMax - (span / 2.0f); - final float offset = span * scale / 2.0f; + float offset = span * scale / 2.0f; + final RectRegion limits = plot.getOuterLimits(); if (isHorizontal ) { - final RectRegion limits = plot.getOuterLimits(); + // zoom limited and increment by value StepMode? + if (zoomLimit == ZoomLimit.MIN_TICKS) { + // make sure we do not zoom in too far (there should be at least one grid line visible) + if (plot.getDomainStepValue() > (scale*span)) { + offset = (float)(plot.getDomainStepValue() / 2.0f); + } + } + newRect.left = midPoint - offset; newRect.right = midPoint + offset; if(limits.isFullyDefined()) { @@ -347,7 +403,14 @@ protected void calculateZoom(RectF newRect, float scale, boolean isHorizontal) { } } } else { - final RectRegion limits = plot.getOuterLimits(); + // zoom limited and increment by value StepMode? + if (zoomLimit == ZoomLimit.MIN_TICKS) { + // make sure we do not zoom in too far (there should be at least one grid line visible) + if (plot.getRangeStepValue() > (scale*span)) { + offset = (float)(plot.getRangeStepValue() / 2.0f); + } + } + newRect.top = midPoint - offset; newRect.bottom = midPoint + offset; if(limits.isFullyDefined()) { @@ -377,6 +440,14 @@ public void setZoom(Zoom zoom) { this.zoom = zoom; } + public ZoomLimit getZoomLimit() { + return zoomLimit; + } + + public void setZoomLimit(ZoomLimit zoomLimit) { + this.zoomLimit = zoomLimit; + } + public View.OnTouchListener getDelegate() { return delegate; } diff --git a/androidplot-core/src/test/java/com/androidplot/xy/PanZoomTest.java b/androidplot-core/src/test/java/com/androidplot/xy/PanZoomTest.java index 6bb7fb20..50d3c16c 100644 --- a/androidplot-core/src/test/java/com/androidplot/xy/PanZoomTest.java +++ b/androidplot-core/src/test/java/com/androidplot/xy/PanZoomTest.java @@ -178,6 +178,61 @@ public void testZoom() { } + @Test + public void testLimitZoom() { + double[] inc_domain = new double[]{10,50,100}; + double[] inc_range = new double[]{20,50}; + + xyPlot = spy(new InstrumentedXYPlot(getContext())); + xyPlot.setDomainBoundaries(0, 20, BoundaryMode.FIXED); + xyPlot.setRangeBoundaries(0, 30, BoundaryMode.FIXED); + xyPlot.setDomainStepModel(new StepModelFit(xyPlot.getBounds().getxRegion(), inc_domain, 5)); + xyPlot.setRangeStepModel(new StepModelFit(xyPlot.getBounds().getyRegion(), inc_range, 5)); + xyPlot.redraw(); + + PanZoom panZoom = spy(new PanZoom(xyPlot, PanZoom.Pan.BOTH, PanZoom.Zoom.SCALE, PanZoom.ZoomLimit.MIN_TICKS)); + + // cap our pan/zoom boundaries: + xyPlot.getOuterLimits().set(0, 20, 0, 30); + + panZoom.setFingersRect(new RectF(0, 0, 20, 20)); + + InOrder inOrder = inOrder(xyPlot); + inOrder.verify(xyPlot).setDomainBoundaries(0, 20, BoundaryMode.FIXED); + + // should NOT result in a 2x zoom on domain centerpoint, but in a zoom to + // the minimum spacing 10 and 20 respectively + panZoom.zoom(TestUtils.newPointerDownEvent(0, 0, 40, 40)); + inOrder.verify(xyPlot).setDomainBoundaries(5f, 15f, BoundaryMode.FIXED); + inOrder.verify(xyPlot).setRangeBoundaries(5f, 25f, BoundaryMode.FIXED); + inOrder.verify(xyPlot).redraw(); + + // to zoom in beyond min limits + panZoom.setZoomLimit(PanZoom.ZoomLimit.OUTER); + + // should result in another 2x zoom on domain centerpoint: + panZoom.zoom(TestUtils.newPointerDownEvent(0, 0, 80, 80)); + inOrder.verify(xyPlot).setDomainBoundaries(7.5f, 12.5f, BoundaryMode.FIXED); + inOrder.verify(xyPlot).setRangeBoundaries(10f, 20f, BoundaryMode.FIXED); + inOrder.verify(xyPlot).redraw(); + + // back to limited zoom + panZoom.setZoomLimit(PanZoom.ZoomLimit.MIN_TICKS); + + // try to zoom in further, should snap back to min limit: + panZoom.zoom(TestUtils.newPointerDownEvent(0, 0, 90, 90)); + inOrder.verify(xyPlot).setDomainBoundaries(5f, 15f, BoundaryMode.FIXED); + inOrder.verify(xyPlot).setRangeBoundaries(5f, 25f, BoundaryMode.FIXED); + inOrder.verify(xyPlot).redraw(); + + // redraw should not be called again + inOrder.verify(xyPlot, never()).redraw(); + + // make sure no panning took place during these zoom ops: + verify(panZoom, never()).pan(any(MotionEvent.class)); + + } + @Test public void testFingerDistance() { PanZoom panZoom = spy(new PanZoom(xyPlot, PanZoom.Pan.BOTH, PanZoom.Zoom.SCALE)); diff --git a/demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java b/demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java index 6d7cd9e0..6ddfdc71 100644 --- a/demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java @@ -78,7 +78,7 @@ public void onClick(View view) { plot.setBorderStyle(Plot.BorderStyle.NONE, null, null); - panZoom = PanZoom.attach(plot); + panZoom = PanZoom.attach(plot, PanZoom.Pan.BOTH, PanZoom.Zoom.STRETCH_BOTH, PanZoom.ZoomLimit.MIN_TICKS); plot.getOuterLimits().set(0, 3000, 0, 1000); initSpinners(); From 2ff46c3d99b5fa660e923f596ea06d8fa3ccd904 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Mon, 20 Mar 2017 07:31:44 -0500 Subject: [PATCH 10/81] Update README.md readme tweak to account for changes to Github's md parsing. --- README.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index b07eb9bd..de4e8522 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,7 @@ compatible with all versions of Android from 1.6 onward and is **used by over If you enjoy the lib, please [rate us on codix.io](http://codix.io/repo/halfhp/androidplot)! - - - - - - - + **Features:** From d9a2f40e3449ed2b34d04bf532a404ed85c88acb Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Sat, 25 Mar 2017 12:19:15 -0500 Subject: [PATCH 11/81] Updates examples / docs for saving and restoring series data to the instance state. (#35) * uprevd to latest, gradle, build tools etc. * Refactored examples and documentation to no longer use the SeriesRegistry to save/restore instance state. Added a simple example of saving/restoring just the series array data into TimeSeriesActivty. * fixed unit test location paths --- androidplot-core/build.gradle | 1 + .../com/androidplot/xy/XYGraphWidget.java | 2 +- build.gradle | 2 +- circle.yml | 4 +- demoapp-wearable/build.gradle | 2 +- demoapp/build.gradle | 1 + .../androidplot/demos/DemoApplication.java | 3 - .../demos/SimpleXYPlotActivity.java | 2 +- .../androidplot/demos/TimeSeriesActivity.java | 107 ++++++++++++------ .../demos/TouchZoomExampleActivity.java | 12 +- .../main/res/layout/time_series_example.xml | 14 ++- demoapp/src/main/res/values/strings.xml | 2 +- docs/advanced_xy_plot.md | 42 +++---- docs/xyplot.md | 6 +- gradle/wrapper/gradle-wrapper.properties | 20 +--- 15 files changed, 110 insertions(+), 110 deletions(-) diff --git a/androidplot-core/build.gradle b/androidplot-core/build.gradle index 2ead754f..5ec463c3 100644 --- a/androidplot-core/build.gradle +++ b/androidplot-core/build.gradle @@ -38,6 +38,7 @@ android { lintOptions { abortOnError false } + buildToolsVersion '25.0.0' } group = 'com.androidplot' 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 f0f6ef38..21c512f6 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java @@ -135,7 +135,7 @@ public static class LineLabelRenderer { public void drawLabel(Canvas canvas, LineLabelStyle style, Number val, float x, float y, boolean isOrigin) { final int canvasState = canvas.save(); try { - final String txt = style.format.format(val.doubleValue()); + final String txt = style.format.format(val); canvas.rotate(style.getRotation(), x, y); drawLabel(canvas, txt, style.getPaint(), x, y, isOrigin); } finally { diff --git a/build.gradle b/build.gradle index 51463dd9..c74013b9 100644 --- a/build.gradle +++ b/build.gradle @@ -38,7 +38,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0' + classpath 'com.android.tools.build:gradle:2.3.0' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7' classpath 'com.vanniktech:gradle-android-junit-jacoco-plugin:0.5.0' diff --git a/circle.yml b/circle.yml index cb73691c..baf2d393 100644 --- a/circle.yml +++ b/circle.yml @@ -32,12 +32,12 @@ test: # junit xml report: - mkdir -p $CIRCLE_TEST_REPORTS/junit-xml/ - - find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} $CIRCLE_TEST_REPORTS/junit-xml/ \; + - find . -type f -regex ".*/build/test-results/testReleaseUnitTest/.*xml" -exec cp {} $CIRCLE_TEST_REPORTS/junit-xml/ \; # junit html report: # TODO: recursively copy subdirs etc - mkdir -p $CIRCLE_TEST_REPORTS/junit-html/ - - cp -r ${HOME}/${CIRCLE_PROJECT_REPONAME}/androidplot-core/build/reports/tests/release/* $CIRCLE_TEST_REPORTS/junit-html/ + - cp -r ${HOME}/${CIRCLE_PROJECT_REPONAME}/androidplot-core/build/reports/tests/testReleaseUnitTest/* $CIRCLE_TEST_REPORTS/junit-html/ # lint report: - mkdir -p $CIRCLE_TEST_REPORTS/lint/ diff --git a/demoapp-wearable/build.gradle b/demoapp-wearable/build.gradle index 98d5307d..75997b20 100644 --- a/demoapp-wearable/build.gradle +++ b/demoapp-wearable/build.gradle @@ -19,7 +19,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.2.3' + classpath 'com.android.tools.build:gradle:2.3.0' } } apply plugin: 'com.android.application' diff --git a/demoapp/build.gradle b/demoapp/build.gradle index 141a6f80..8702a396 100644 --- a/demoapp/build.gradle +++ b/demoapp/build.gradle @@ -76,6 +76,7 @@ android { lintOptions { abortOnError false } + buildToolsVersion '25.0.0' } play { diff --git a/demoapp/src/main/java/com/androidplot/demos/DemoApplication.java b/demoapp/src/main/java/com/androidplot/demos/DemoApplication.java index f8c00eae..9c931a73 100644 --- a/demoapp/src/main/java/com/androidplot/demos/DemoApplication.java +++ b/demoapp/src/main/java/com/androidplot/demos/DemoApplication.java @@ -4,9 +4,6 @@ import com.squareup.leakcanary.*; -/** - * Created by halfhp on 10/1/16. - */ public class DemoApplication extends Application { @Override public void onCreate() { diff --git a/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java b/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java index 904f02c7..8cff7fed 100644 --- a/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java @@ -72,7 +72,7 @@ public void onCreate(Bundle savedInstanceState) PixelUtils.dpToPix(20), PixelUtils.dpToPix(15)}, 0)); - // just for fun, add some smoothing to the lines: + // (optional) add some smoothing to the lines: // see: http://androidplot.com/smooth-curves-and-androidplot/ series1Format.setInterpolationParams( new CatmullRomInterpolator.Params(10, CatmullRomInterpolator.Type.Centripetal)); diff --git a/demoapp/src/main/java/com/androidplot/demos/TimeSeriesActivity.java b/demoapp/src/main/java/com/androidplot/demos/TimeSeriesActivity.java index e457ad0f..684d9388 100644 --- a/demoapp/src/main/java/com/androidplot/demos/TimeSeriesActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/TimeSeriesActivity.java @@ -20,17 +20,23 @@ import android.graphics.*; import android.os.Bundle; +import com.androidplot.util.PixelUtils; import com.androidplot.xy.SimpleXYSeries; import com.androidplot.xy.XYSeries; import com.androidplot.xy.*; import java.text.*; import java.util.Arrays; +import java.util.Calendar; import java.util.Date; +import java.util.GregorianCalendar; public class TimeSeriesActivity extends Activity { + private static final String SERIES_TITLE = "Signthings in USA"; + private XYPlot plot1; + private SimpleXYSeries series; @Override public void onCreate(Bundle savedInstanceState) { @@ -38,43 +44,36 @@ public void onCreate(Bundle savedInstanceState) { setContentView(R.layout.time_series_example); plot1 = (XYPlot) findViewById(R.id.plot1); - Number[] numSightings = {5, 8, 6, 9, 3, 8, 5}; - - // an array of years in milliseconds: - Number[] years = { - 978307200, // 2001 - 998309300, - 1009843200, // 2002 - 1041379200, // 2003 - 1052012100, - 1072915200, // 2004 - 1104537600 // 2005 + + // these will be our domain index labels: + final Date[] years = { + new GregorianCalendar(2001, Calendar.JANUARY, 1).getTime(), + new GregorianCalendar(2001, Calendar.JULY, 1).getTime(), + new GregorianCalendar(2002, Calendar.JANUARY, 1).getTime(), + new GregorianCalendar(2002, Calendar.JULY, 1).getTime(), + new GregorianCalendar(2003, Calendar.JANUARY, 1).getTime(), + new GregorianCalendar(2003, Calendar.JULY, 1).getTime(), + new GregorianCalendar(2004, Calendar.JANUARY, 1).getTime(), + new GregorianCalendar(2004, Calendar.JULY, 1).getTime(), + new GregorianCalendar(2005, Calendar.JANUARY, 1).getTime(), + new GregorianCalendar(2005, Calendar.JULY, 1).getTime() }; - // create our series from our array of nums: - XYSeries series2 = new SimpleXYSeries( - Arrays.asList(years), - Arrays.asList(numSightings), - "Sightings in USA"); + + addSeries(savedInstanceState); + + plot1.setRangeBoundaries(0, 10, BoundaryMode.FIXED); plot1.getGraph().getGridBackgroundPaint().setColor(Color.WHITE); plot1.getGraph().getDomainGridLinePaint().setColor(Color.BLACK); plot1.getGraph().getDomainGridLinePaint(). - setPathEffect(new DashPathEffect(new float[] {1, 1}, 1)); + setPathEffect(new DashPathEffect(new float[]{1, 1}, 1)); plot1.getGraph().getRangeGridLinePaint().setColor(Color.BLACK); plot1.getGraph().getRangeGridLinePaint(). - setPathEffect(new DashPathEffect(new float[] {1, 1}, 1)); + setPathEffect(new DashPathEffect(new float[]{1, 1}, 1)); plot1.getGraph().getDomainOriginLinePaint().setColor(Color.BLACK); plot1.getGraph().getRangeOriginLinePaint().setColor(Color.BLACK); - // setup our line fill paint to be a slightly transparent gradient: - Paint lineFill = new Paint(); - lineFill.setAlpha(200); - - LineAndPointFormatter formatter = - new LineAndPointFormatter(Color.rgb(0, 0, 0), Color.BLUE, Color.RED, null); - formatter.setFillPaint(lineFill); plot1.getGraph().setPaddingRight(2); - plot1.addSeries(series2, formatter); // draw a domain tick for each year: plot1.setDomainStep(StepMode.SUBDIVIDE, years.length); @@ -93,17 +92,15 @@ public void onCreate(Bundle savedInstanceState) { // create a simple date format that draws on the year portion of our timestamp. // see http://download.oracle.com/javase/1.4.2/docs/api/java/text/SimpleDateFormat.html // for a full description of SimpleDateFormat. - private SimpleDateFormat dateFormat = new SimpleDateFormat("MM-yyyy"); + private final SimpleDateFormat dateFormat = new SimpleDateFormat("MMM yyyy"); @Override - public StringBuffer format(Object obj, StringBuffer toAppendTo, - FieldPosition pos) { - - // because our timestamps are in seconds and SimpleDateFormat expects milliseconds - // we multiply our timestamp by 1000: - long timestamp = ((Number) obj).longValue() * 1000; - Date date = new Date(timestamp); - return dateFormat.format(date, toAppendTo, pos); + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + + // this rounding is necessary to avoid precision loss when converting from + // double back to int: + int yearIndex = (int) Math.round(((Number) obj).doubleValue()); + return dateFormat.format(years[yearIndex], toAppendTo, pos); } @Override @@ -113,4 +110,44 @@ public Object parseObject(String source, ParsePosition pos) { } }); } + + /** + * Instantiates our XYSeries, checking the current savedInstanceState for existing series data + * to avoid having to regenerate on each resume. If your series data is small and easy to + * regenerate (as it is here) then you can skip saving/restoring your series data to + * savedInstanceState. + * @param savedInstanceState Current saved instance state, if any; may be null. + */ + private void addSeries(Bundle savedInstanceState) { + Number[] yVals; + + if(savedInstanceState != null) { + yVals = (Number[]) savedInstanceState.getSerializable(SERIES_TITLE); + } else { + yVals = new Number[]{5, 8, 6, 9, 3, 8, 5, 4, 7, 4}; + } + + // create our series from our array of nums: + series = new SimpleXYSeries(Arrays.asList(yVals), + SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, SERIES_TITLE); + + LineAndPointFormatter formatter = + new LineAndPointFormatter(Color.rgb(0, 0, 0), Color.RED, Color.RED, null); + formatter.getVertexPaint().setStrokeWidth(PixelUtils.dpToPix(10)); + formatter.getLinePaint().setStrokeWidth(PixelUtils.dpToPix(5)); + + // setup our line fill paint to be a slightly transparent gradient: + Paint lineFill = new Paint(); + lineFill.setAlpha(200); + + formatter.setFillPaint(lineFill); + + plot1.addSeries(series, formatter); + } + + @Override + public void onSaveInstanceState(Bundle bundle) { + // persist our series data so we don't have to regenerate each time: + bundle.putSerializable(SERIES_TITLE, series.getyVals().toArray(new Number[]{})); + } } \ 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 6ddfdc71..94d1efa5 100644 --- a/demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java @@ -85,20 +85,10 @@ public void onClick(View view) { // enable autoselect of sampling level based on visible boundaries: plot.getRegistry().setEstimator(new ZoomEstimator()); - if(savedInstanceState != null && savedInstanceState.containsKey("seriesRegistry")) { - XYSeriesRegistry registry = (XYSeriesRegistry) savedInstanceState.getSerializable("seriesRegistry"); - plot.setRegistry(registry); - } else { - generateSeriesData(); - } + generateSeriesData(); reset(); } - @Override - public void onSaveInstanceState(Bundle bundle) { - bundle.putSerializable("seriesRegistry", plot.getRegistry()); - } - private void reset() { plot.setDomainBoundaries(0, 10000, BoundaryMode.FIXED); plot.setRangeBoundaries(0, 1000, BoundaryMode.FIXED); diff --git a/demoapp/src/main/res/layout/time_series_example.xml b/demoapp/src/main/res/layout/time_series_example.xml index 02b5c184..2bcaa251 100644 --- a/demoapp/src/main/res/layout/time_series_example.xml +++ b/demoapp/src/main/res/layout/time_series_example.xml @@ -28,10 +28,16 @@ android:layout_width="fill_parent" android:layout_height="fill_parent" ap:title="@string/ts_plot1_title" + ap:paddingTop="10dp" + ap:paddingBottom="10dp" + ap:paddingLeft="10dp" + ap:paddingRight="10dp" + ap:backgroundColor="@color/ap_white" + ap:graphBackgroundColor="@color/ap_white" ap:lineLabelRotationBottom="-45" - ap:gridInsetLeft="30dp" - ap:gridInsetBottom="25dp" - ap:gridInsetRight="20dp" - ap:lineLabelInsetBottom="15dp" + ap:gridInsetLeft="35dp" + ap:gridInsetBottom="40dp" + ap:lineLabelInsetLeft="25dp" + ap:lineLabelInsetBottom="25dp" renderMode="use_main_thread"/> \ No newline at end of file diff --git a/demoapp/src/main/res/values/strings.xml b/demoapp/src/main/res/values/strings.xml index 37ade4d0..9d2697f4 100644 --- a/demoapp/src/main/res/values/strings.xml +++ b/demoapp/src/main/res/values/strings.xml @@ -19,5 +19,5 @@ Androidplot Demos A Simple XY Plot Time Series - Yearly UFO Sightings + UFO Sightings diff --git a/docs/advanced_xy_plot.md b/docs/advanced_xy_plot.md index 4fa98302..76b1552f 100644 --- a/docs/advanced_xy_plot.md +++ b/docs/advanced_xy_plot.md @@ -139,32 +139,16 @@ new LTTBSampler().run(originalSeries, sampledSeries); Currently LTTBSampler is the only available implementation. # Storing series data in onSaveInstanceState -If your series data requires a non trivial amount of preprocessing (subsampling etc.) or your data samples -stream in periodically, you'll likely want to persist your series data. - -Androidplot provides a simplified mechanism for preserving any registered series and formatter data -provided they are serializable implementations. (All implementations of XYSeries that ship -with Androidplot are serializable) - -To persist series / formatter data: -```java -// persist plot series / formatter configuration: -@Override -public void onSaveInstanceState(Bundle bundle) { - bundle.putSerializable("seriesRegistry", plot.getRegistry()); -} -``` - -To restore series / formatter data: -```java -public void onCreate(Bundle savedInstanceState) { - ... - if(savedInstanceState != null && savedInstanceState.containsKey("seriesRegistry")) { - XYSeriesRegistry registry = (XYSeriesRegistry) savedInstanceState.getSerializable("seriesRegistry"); - plot.setRegistry(registry); - } else { - // first-time setup as usual - ... - } -} -``` \ No newline at end of file +If your series data requires a non trivial amount of preprocessing (subsampling etc.) or your data comes +from a dynamic source, you'll likely want to persist your series data when your Activity saves its +instance state. There are a few caveats to be aware of: + +* You can only persist about 1mb worth of data at a time so if your series data is much larger than that +you'll need to find a creative solution to the problem +* Due to [quirks in the way Android persists data](http://stackoverflow.com/questions/12300886/linkedlist-put-into-intent-extra-gets-recast-to-arraylist-when-retrieving-in-nex) +`XYSeries` implementations such as `SimpleXYSeries` that use `LinkedList` instances to store data cannot be serialized directly. +* Formatters generally cannot be persisted as they typically contain instances of `Paint` that cannot be serialized directly.. + +Due to these limitations we suggest storing `XYSeries` data into an array or `ArrayList` and using that to +instantiate your `XYSeries`. The DemoApp's [Time Series Example](../demoapp/src/main/java/com/androidplot/demos/TimeSeriesActivity.java) +contains a full source example. \ No newline at end of file diff --git a/docs/xyplot.md b/docs/xyplot.md index 924afe29..1e363d12 100644 --- a/docs/xyplot.md +++ b/docs/xyplot.md @@ -368,12 +368,12 @@ particularly well suited for downsampling `XYSeries` data. LineAndPointFormatter format = new LineAndPointFormatter(...); format.getLinePaint().setAntiAlias(false); ``` -# Screen<->Series Conversion +# Converting Values Because the coordinate system used by your `XYSeries` data is almost always different than the screen coordinate system upon which the data is rendered, you'll often need to convert from one system to the other. `XYPlot` provides convenience methods for this purpose: -To convert screen vals to series vals: +## Screen to Series Conversion ```java // x float screenX = ... @@ -388,7 +388,7 @@ PointF screenCoords = ... XYCoords xy = plot.screenToSeries(screenCoords) ``` -To convert series vals to screen vals: +## Series to Screen Conversion ```java // x Number x = ... diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f7eef2f8..0429c501 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,22 +1,6 @@ -# -# Copyright 2015 AndroidPlot.com -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -#Wed Dec 02 22:16:29 CST 2015 +#Thu Mar 23 07:48:11 CDT 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip From 8253be0f6acbfca9643cef16b5bfab509d47b760 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Sat, 25 Mar 2017 12:29:45 -0500 Subject: [PATCH 12/81] Updated quickstart to reference 1.4.2 --- docs/quickstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quickstart.md b/docs/quickstart.md index ce7e5426..57bd090c 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -14,7 +14,7 @@ To use the library in your gradle project add the following to your build.gradle ```groovy dependencies { - compile "com.androidplot:androidplot-core:1.4.1" + compile "com.androidplot:androidplot-core:1.4.2" } ``` From 3613d4e6a3c1807a0dfde074df1b357c70091098 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Sat, 25 Mar 2017 12:46:33 -0500 Subject: [PATCH 13/81] Updated release notes for 1.4.2 --- docs/release_notes.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/release_notes.md b/docs/release_notes.md index a593416e..995cfbbc 100644 --- a/docs/release_notes.md +++ b/docs/release_notes.md @@ -2,6 +2,11 @@ For details on what to expect in general when updating to a new version of Androiplot, check out the [versioning doc](versioning.md). +# 1.4.2 +* (#32) New step mode: `INCREMENT_BY_FIT`. +* (#33) PanZoom support for 'INCREMENT_BY_FIT'. +* (#34) Removed examples and documentation for serializing `SeriesRegistry` to preserve state. + # 1.4.1 * (#26) Fixed an NPE issue when drawing null values with a PointLabeler. * Fixed a broken link in Quickstart doc. From ba68fb2cda3915e47fd45b5ab935231625d8b68d Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Sat, 25 Mar 2017 15:59:46 -0500 Subject: [PATCH 14/81] uprev to 1.4.3 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c74013b9..11e85b40 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ ext { theCompileSdkVersion = 24 theTargetSdkVersion = 24 theMinSdkVersion = 5 - theVersionName = '1.4.2' + theVersionName = '1.4.3' theVersionCode = 0 } From b6fbe2f669907f1750c806f221230fe36a874860 Mon Sep 17 00:00:00 2001 From: Lucas Palmer Date: Sat, 25 Mar 2017 17:09:33 -0400 Subject: [PATCH 15/81] Have to check whether segment is selected proir to deselecting all segments. (#36) --- .../main/java/com/androidplot/demos/SimplePieChartActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demoapp/src/main/java/com/androidplot/demos/SimplePieChartActivity.java b/demoapp/src/main/java/com/androidplot/demos/SimplePieChartActivity.java index 850bc4d8..03b5b12b 100644 --- a/demoapp/src/main/java/com/androidplot/demos/SimplePieChartActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/SimplePieChartActivity.java @@ -73,9 +73,9 @@ public boolean onTouch(View view, MotionEvent motionEvent) { if(pie.getPie().containsPoint(click)) { Segment segment = pie.getRenderer(PieRenderer.class).getContainingSegment(click); - deselectAll(); if(segment != null) { final boolean isSelected = getFormatter(segment).getOffset() != 0; + deselectAll(); setSelected(segment, !isSelected); pie.redraw(); } From bd69fecb3d3bd3eae901a934df196abda58ed7c6 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Thu, 6 Apr 2017 21:01:26 -0500 Subject: [PATCH 16/81] SDK / Tools Updates + More Unit Tests (#38) * update target SDK to 25 / update gradle plugin to 2.3.1. also added caching to sdk deps for circle cfg. * added support annotations. added unit tests * more unit test coverage --- androidplot-core/build.gradle | 1 + .../src/main/java/com/androidplot/Plot.java | 14 +- .../androidplot/ui/widget/EmptyWidget.java | 34 -- .../ui/widget/TextLabelWidget.java | 17 +- .../java/com/androidplot/util/LayerHash.java | 24 ++ .../com/androidplot/util/PlotStatistics.java | 10 +- .../com/androidplot/util/SeriesUtils.java | 25 +- .../androidplot/xy/LineAndPointRenderer.java | 1 - .../java/com/androidplot/xy/RectRegion.java | 35 -- .../java/com/androidplot/xy/StepRenderer.java | 1 - .../main/java/com/androidplot/xy/XYPlot.java | 21 +- .../test/java/com/androidplot/PlotTest.java | 312 +++++++++++------- .../ui/widget/TextLabelWidgetTest.java | 82 +++++ .../com/androidplot/util/LayerHashTest.java | 65 +++- .../androidplot/util/PlotStatisticsTest.java | 68 ++++ .../com/androidplot/util/SeriesUtilsTest.java | 8 + .../xy/LineAndPointRendererTest.java | 42 +++ .../com/androidplot/xy/RectRegionTest.java | 52 --- .../androidplot/xy/SimpleXYSeriesTest.java | 40 +++ .../java/com/androidplot/xy/XYPlotTest.java | 169 ++++++++-- build.gradle | 8 +- circle.yml | 4 +- demoapp-wearable/build.gradle | 2 +- 23 files changed, 672 insertions(+), 363 deletions(-) delete mode 100644 androidplot-core/src/main/java/com/androidplot/ui/widget/EmptyWidget.java create mode 100644 androidplot-core/src/test/java/com/androidplot/ui/widget/TextLabelWidgetTest.java create mode 100644 androidplot-core/src/test/java/com/androidplot/util/PlotStatisticsTest.java diff --git a/androidplot-core/build.gradle b/androidplot-core/build.gradle index 5ec463c3..e063b01f 100644 --- a/androidplot-core/build.gradle +++ b/androidplot-core/build.gradle @@ -50,6 +50,7 @@ def gitUrl = 'https://github.com/halfhp/androidplot.git' dependencies { compile 'com.halfhp.fig:figlib:1.0.3' + compile 'com.android.support:support-annotations:24.2.0' testCompile "org.mockito:mockito-core:1.10.19" testCompile group: 'junit', name: 'junit', version: '4.12' testCompile "org.robolectric:robolectric:3.1" diff --git a/androidplot-core/src/main/java/com/androidplot/Plot.java b/androidplot-core/src/main/java/com/androidplot/Plot.java index 0fe39d2b..f2eab84a 100644 --- a/androidplot-core/src/main/java/com/androidplot/Plot.java +++ b/androidplot-core/src/main/java/com/androidplot/Plot.java @@ -164,7 +164,7 @@ public enum RenderMode { private final BufferedCanvas pingPong = new BufferedCanvas(); // used to get rid of flickering when drawing offScreenBitmap to the visible Canvas. - private final Object renderSynch = new Object(); + private final Object renderSync = new Object(); private HashMap, RendererType> renderers; @@ -390,13 +390,13 @@ public void run() { renderOnCanvas(c); pingPong.swap(); } - synchronized (renderSynch) { + synchronized (renderSync) { postInvalidate(); // prevent this thread from becoming an orphan // after the view is destroyed if (keepRunning) { try { - renderSynch.wait(); + renderSync.wait(); } catch (InterruptedException e) { keepRunning = false; } @@ -712,8 +712,8 @@ public void redraw() { // if the render thread is idle, so we know that we won't have to wait to // obtain a lock. if (isIdle) { - synchronized (renderSynch) { - renderSynch.notify(); + synchronized (renderSync) { + renderSync.notify(); } } } else if(renderMode == RenderMode.USE_MAIN_THREAD) { @@ -738,9 +738,9 @@ public synchronized void layout(final DisplayDimensions dims) { @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - synchronized(renderSynch) { + synchronized(renderSync) { keepRunning = false; - renderSynch.notify(); + renderSync.notify(); } } diff --git a/androidplot-core/src/main/java/com/androidplot/ui/widget/EmptyWidget.java b/androidplot-core/src/main/java/com/androidplot/ui/widget/EmptyWidget.java deleted file mode 100644 index f6c30439..00000000 --- a/androidplot-core/src/main/java/com/androidplot/ui/widget/EmptyWidget.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2015 AndroidPlot.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.androidplot.ui.widget; - -import android.graphics.Canvas; -import android.graphics.RectF; -import com.androidplot.exception.PlotRenderException; -import com.androidplot.ui.LayoutManager; -import com.androidplot.ui.Size; - -public class EmptyWidget extends Widget { - - public EmptyWidget(LayoutManager layoutManager, Size size) { - super(layoutManager, size); - } - @Override - protected void doOnDraw(Canvas canvas, RectF widgetRect) throws PlotRenderException { - // nothing to do - } -} diff --git a/androidplot-core/src/main/java/com/androidplot/ui/widget/TextLabelWidget.java b/androidplot-core/src/main/java/com/androidplot/ui/widget/TextLabelWidget.java index 9f47aea9..8c6e84cd 100644 --- a/androidplot-core/src/main/java/com/androidplot/ui/widget/TextLabelWidget.java +++ b/androidplot-core/src/main/java/com/androidplot/ui/widget/TextLabelWidget.java @@ -21,13 +21,9 @@ import com.androidplot.util.FontUtils; public class TextLabelWidget extends Widget { - private static final String TAG = TextLabelWidget.class.getName(); - private String text; private Paint labelPaint; - private TextOrientation orientation; - private boolean autoPackEnabled = true; { @@ -67,16 +63,12 @@ public void onPostInit() { } } - //protected abstract String getText(); - /** * Sets the dimensions of the widget to exactly contain the text contents */ public void pack() { - //Log.d(TAG, "Packing..."); Rect size = FontUtils.getStringDimensions(text, getLabelPaint()); if(size == null) { - //Log.w(TAG, "Attempt to pack empty text."); return; } switch(orientation) { @@ -103,14 +95,11 @@ public void doOnDraw(Canvas canvas, RectF widgetRect) { if(text == null || text.length() == 0) { return; } - //FontUtils.getStringDimensions(text, labelPaint); + float vOffset = labelPaint.getFontMetrics().descent; PointF start = getAnchorCoordinates(widgetRect, Anchor.CENTER); - // BEGIN ROTATION CALCULATION - //int canvasState = canvas.save(Canvas.ALL_SAVE_FLAG); - try { canvas.save(Canvas.ALL_SAVE_FLAG); canvas.translate(start.x, start.y); @@ -129,11 +118,8 @@ public void doOnDraw(Canvas canvas, RectF widgetRect) { } canvas.drawText(text, 0, vOffset, labelPaint); } finally { - //canvas.restoreToCount(canvasState); canvas.restore(); } - - // END ROTATION CALCULATION } public Paint getLabelPaint() { @@ -173,7 +159,6 @@ public void setAutoPackEnabled(boolean autoPackEnabled) { } public void setText(String text) { - //Log.d(TAG, "Setting textLabel to: " + text); this.text = text; if(autoPackEnabled) { pack(); diff --git a/androidplot-core/src/main/java/com/androidplot/util/LayerHash.java b/androidplot-core/src/main/java/com/androidplot/util/LayerHash.java index 61e25c97..f11737a6 100644 --- a/androidplot-core/src/main/java/com/androidplot/util/LayerHash.java +++ b/androidplot-core/src/main/java/com/androidplot/util/LayerHash.java @@ -153,4 +153,28 @@ public synchronized boolean remove(KeyType key) { return false; } } + + public ValueType getTop() { + return hash.get(zlist.getLast()); + } + + public ValueType getBottom() { + return hash.get(zlist.getFirst()); + } + + public ValueType getAbove(KeyType key) { + final int index = zlist.indexOf(key); + if(index >= 0 && index < size() - 1) { + return hash.get(zlist.get(index + 1)); + } + return null; + } + + public ValueType getBeneath(KeyType key) { + final int index = zlist.indexOf(key); + if(index > 0) { + return hash.get(zlist.get(index - 1)); + } + return null; + } } diff --git a/androidplot-core/src/main/java/com/androidplot/util/PlotStatistics.java b/androidplot-core/src/main/java/com/androidplot/util/PlotStatistics.java index 1d597b9e..b56337ed 100644 --- a/androidplot-core/src/main/java/com/androidplot/util/PlotStatistics.java +++ b/androidplot-core/src/main/java/com/androidplot/util/PlotStatistics.java @@ -39,6 +39,7 @@ public class PlotStatistics implements PlotListener { long latencySamples = 0; long latencySum = 0; String annotationString = ""; + private boolean annotatePlotEnabled; private Paint paint; { @@ -49,11 +50,6 @@ public class PlotStatistics implements PlotListener { resetCounters(); } - - private boolean annotatePlotEnabled; - - - public PlotStatistics(long updateDelayMs, boolean annotatePlotEnabled) { this.updateDelayMs = updateDelayMs; this.annotatePlotEnabled = annotatePlotEnabled; @@ -109,4 +105,8 @@ public void onAfterDraw(Plot source, Canvas canvas) { latencySamples++; annotatePlot(source, canvas); } + + public void setEnabled(boolean isEnabled) { + this.annotatePlotEnabled = isEnabled; + } } diff --git a/androidplot-core/src/main/java/com/androidplot/util/SeriesUtils.java b/androidplot-core/src/main/java/com/androidplot/util/SeriesUtils.java index 2b269757..a9f09192 100644 --- a/androidplot-core/src/main/java/com/androidplot/util/SeriesUtils.java +++ b/androidplot-core/src/main/java/com/androidplot/util/SeriesUtils.java @@ -84,6 +84,9 @@ public static RectRegion minMax(XYConstraints constraints, XYSeries... seriesArr // if this is an advanced xy series then minMax have already been calculated for us: if(series instanceof FastXYSeries) { final RectRegion b = ((FastXYSeries) series).minMax(); + if(b == null) { + continue; + } if(constraints == null) { bounds.union(b); } else { @@ -259,26 +262,6 @@ public static Region minMax(List... lists) { return minMax(new Region(), lists); } - public static void main(String[] args) { - - // seed the list: - ArrayList values = new ArrayList<>(); - for (int i = 0; i < 1000000; i++) { - values.add(Math.random() * 100); - } - final int numIterations = 20; - long sumTime = 0; - for(int j = 0; j < numIterations; j++) { - final long startTime = System.currentTimeMillis(); - Region bounds = minMax(values); - final long thisIteration = System.currentTimeMillis() - startTime; - System.out.println("thisIteration took: " + thisIteration + "ms"); - sumTime += thisIteration; - } - - System.out.println("Benchmark avg:" + (sumTime / numIterations) + "ms."); - } - /** * Determine the XVal order of an XYSeries. If series does not implement {@link OrderedXYSeries} * then {@link com.androidplot.xy.OrderedXYSeries.XOrder#NONE} is assumed. @@ -287,6 +270,6 @@ public static void main(String[] args) { */ public static OrderedXYSeries.XOrder getXYOrder(XYSeries series) { return series instanceof OrderedXYSeries ? - ((OrderedXYSeries) series).getXOrder() : OrderedXYSeries.XOrder.NONE; + ((OrderedXYSeries) series).getXOrder() : OrderedXYSeries.XOrder.NONE; } } diff --git a/androidplot-core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java b/androidplot-core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java index 959cd784..e8c0c23b 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java @@ -124,7 +124,6 @@ protected ArrayList getPointsCache(XYSeries series) { protected void cullPointsCache() { for(XYSeries series : pointsCaches.keySet()) { if(!getPlot().getRegistry().contains(series, LineAndPointFormatter.class)) { - //pointsCaches.put(series, null); pointsCaches.remove(series); } } diff --git a/androidplot-core/src/main/java/com/androidplot/xy/RectRegion.java b/androidplot-core/src/main/java/com/androidplot/xy/RectRegion.java index c2182691..7eeb0d73 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/RectRegion.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/RectRegion.java @@ -344,39 +344,4 @@ public boolean isFullyDefined() { public boolean contains(Number x, Number y) { return getxRegion().contains(x) && getyRegion().contains(y); } - - /** - * Checks to see whether the specified line. Note that this implementation will return - * true even if the line is completely enclosed by this RectRegion. - * WARNING: this implementation has problems. See associated unit test for details. - * @param x1 x-coord of the line beginning - * @param y1 y-coord of the line beginning - * @param x2 x-coord of the line end - * @param y2 y-coord of the line end - * @return True if this RectRegion overlaps any part of the specified line. - */ - public boolean intersectsWithLine(Number x1, Number y1, Number x2, Number y2) { - if(contains(x1, y1) || contains(x2, y2)) { - return true; - } - - // if true, it means that these points exist on different sides of the rect's edges - final boolean x1MinRelation = x1.doubleValue() < getMinX().doubleValue(); - final boolean x2MinRelation = x2.doubleValue() < getMinX().doubleValue(); - final boolean xMinRelation = x1MinRelation &! x2MinRelation; - - final boolean x1MaxRelation = x1.doubleValue() < getMaxX().doubleValue(); - final boolean x2MaxRelation = x2.doubleValue() < getMaxX().doubleValue(); - final boolean xMaxRelation = x1MaxRelation &! x2MaxRelation; - - final boolean y1MinRelation = y1.doubleValue() < getMinY().doubleValue(); - final boolean y2MinRelation = y2.doubleValue() < getMinY().doubleValue(); - final boolean yMinRelation = y1MinRelation &! y2MinRelation; - - final boolean y1MaxRelation = y1.doubleValue() < getMaxY().doubleValue(); - final boolean y2MaxRelation = y2.doubleValue() < getMaxY().doubleValue(); - final boolean yMaxRelation = y1MaxRelation &! y2MaxRelation; - - return ((xMinRelation | xMaxRelation) || getxRegion().contains(x1) & (yMinRelation | yMaxRelation) || getyRegion().contains(y1)); - } } diff --git a/androidplot-core/src/main/java/com/androidplot/xy/StepRenderer.java b/androidplot-core/src/main/java/com/androidplot/xy/StepRenderer.java index 04f0449e..f14608d7 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/StepRenderer.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/StepRenderer.java @@ -23,7 +23,6 @@ * Renders a point as a line with the vertices marked. Requires 2 or more points to * be rendered. */ - public class StepRenderer extends LineAndPointRenderer { public StepRenderer(XYPlot plot) { diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java b/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java index 9f72cf18..33769886 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java @@ -22,6 +22,7 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.PointF; +import android.support.annotation.NonNull; import android.util.AttributeSet; import com.androidplot.*; @@ -595,7 +596,7 @@ private Number applyUserMinMax(Number value, Number min, Number max) { * * @param origin */ - public void centerOnDomainOrigin(Number origin) { + public void centerOnDomainOrigin(@NonNull Number origin) { centerOnDomainOrigin(origin, null, BoundaryMode.AUTO); } @@ -607,9 +608,9 @@ public void centerOnDomainOrigin(Number origin) { * @param extent * @param mode */ - public void centerOnDomainOrigin(Number origin, Number extent, BoundaryMode mode) { + public void centerOnDomainOrigin(@NonNull Number origin, Number extent, BoundaryMode mode) { if (origin == null) { - throw new NullPointerException("Origin param cannot be null."); + throw new IllegalArgumentException("Origin param cannot be null."); } constraints.setDomainFramingModel(XYFramingModel.ORIGIN); setUserDomainOrigin(origin); @@ -626,7 +627,7 @@ public void centerOnDomainOrigin(Number origin, Number extent, BoundaryMode mode * * @param origin */ - public void centerOnRangeOrigin(Number origin) { + public void centerOnRangeOrigin(@NonNull Number origin) { centerOnRangeOrigin(origin, null, BoundaryMode.AUTO); } @@ -639,9 +640,9 @@ public void centerOnRangeOrigin(Number origin) { * @param mode */ @SuppressWarnings("SameParameterValue") - public void centerOnRangeOrigin(Number origin, Number extent, BoundaryMode mode) { + public void centerOnRangeOrigin(@NonNull Number origin, Number extent, BoundaryMode mode) { if (origin == null) { - throw new NullPointerException("Origin param cannot be null."); + throw new IllegalArgumentException("Origin param cannot be null."); } constraints.setRangeFramingModel(XYFramingModel.ORIGIN); setUserRangeOrigin(origin); @@ -1036,12 +1037,12 @@ public synchronized void setUserRangeOrigin(Number origin) { } @SuppressWarnings("SameParameterValue") - protected void setDomainFramingModel(XYFramingModel model) { + protected void setDomainFramingModel(@NonNull XYFramingModel model) { constraints.setDomainFramingModel(model); } @SuppressWarnings("SameParameterValue") - protected void setRangeFramingModel(XYFramingModel model) { + protected void setRangeFramingModel(@NonNull XYFramingModel model) { constraints.setRangeFramingModel(model); } @@ -1089,9 +1090,7 @@ public YValueMarker removeMarker(YValueMarker marker) { * @return */ public int removeMarkers() { - int removed = removeXMarkers(); - removed += removeYMarkers(); - return removed; + return removeXMarkers() + removeYMarkers(); } /** diff --git a/androidplot-core/src/test/java/com/androidplot/PlotTest.java b/androidplot-core/src/test/java/com/androidplot/PlotTest.java index 6c013ea6..dc5e5e88 100644 --- a/androidplot-core/src/test/java/com/androidplot/PlotTest.java +++ b/androidplot-core/src/test/java/com/androidplot/PlotTest.java @@ -23,8 +23,11 @@ import com.androidplot.exception.PlotRenderException; import com.androidplot.test.*; import com.androidplot.ui.*; +import com.androidplot.xy.LineAndPointFormatter; +import com.androidplot.xy.SimpleXYSeries; import com.halfhp.fig.*; import org.junit.Test; +import org.mockito.Mock; import org.robolectric.RuntimeEnvironment; import java.util.ArrayList; import java.util.HashMap; @@ -40,127 +43,8 @@ public class PlotTest extends AndroidplotTest { - static class MockPlotListener implements PlotListener { - - public void onBeforeDraw(Plot source, Canvas canvas) {} - - public void onAfterDraw(Plot source, Canvas canvas) {} - } - - static class MockSeries implements Series { - - public String getTitle() { - return null; - } - - } - - static class MockSeries2 implements Series { - - public String getTitle() { - return null; - } - } - - static class MockSeries3 implements Series { - - public String getTitle() { - return null; - } - } - - static class MockRenderer1 extends SeriesRenderer { - - public MockRenderer1(Plot plot) { - super(plot); - } - - @Override - public void onRender(Canvas canvas, RectF plotArea, Series series, Formatter formatter, RenderStack stack) throws PlotRenderException { - - } - - @Override - public void doDrawLegendIcon(Canvas canvas, RectF rect, Formatter formatter) { - - } - } - static class MockRenderer2 extends SeriesRenderer { - - public MockRenderer2(Plot plot) { - super(plot); - } - - @Override - public void onRender(Canvas canvas, RectF plotArea, Series series, Formatter formatter, RenderStack stack) throws PlotRenderException { - - } - - @Override - public void doDrawLegendIcon(Canvas canvas, RectF rect, Formatter formatter) { - - } - } - - static class MockFormatter1 extends Formatter { - - @Override - public Class getRendererClass() { - return MockRenderer1.class; - } - - @Override - public SeriesRenderer doGetRendererInstance(MockPlot plot) { - return new MockRenderer1(plot); - } - } - - static class MockFormatter2 extends Formatter { - - @Override - public Class getRendererClass() { - return MockRenderer2.class; - } - - @Override - public SeriesRenderer doGetRendererInstance(MockPlot plot) { - return new MockRenderer2(plot); - } - } - - public static class MockSeriesBundle extends SeriesBundle { - - public MockSeriesBundle(MockSeries series, Formatter formatter) { - super(series, formatter); - } - } - - public static class MockPlot extends Plot> { - public MockPlot(String title) { - super(RuntimeEnvironment.application, title); - } - - @Override - protected SeriesRegistry getRegistryInstance() { - return new SeriesRegistry() { - @Override - protected MockSeriesBundle newSeriesBundle( - MockSeries series, Formatter formatter) { - return new MockSeriesBundle(series, formatter); - } - }; - } - - @Override - protected void onPreInit() { - - } - - @Override - protected void processAttrs(TypedArray attrs) { - - } - } + @Mock + SeriesRegistry mockSeriesRegistry; @Test public void testInit_withoutAttrs() throws Exception { @@ -344,7 +228,7 @@ public void testAddListener() throws Exception { plot.addListener(pl2); assertEquals(2, listeners.size()); - + } @Test @@ -397,4 +281,188 @@ public void testConfigure() throws Exception { assertEquals(Plot.RenderMode.USE_BACKGROUND_THREAD, plot.getRenderMode()); assertEquals(Color.parseColor(param3), plot.getBackgroundPaint().getColor()); } + + @Test + public void setTitle_setsTitle() { + Plot plot = new MockPlot("foo"); + plot.setTitle("bar"); + assertEquals("bar", plot.getTitle().getText()); + } + + @Test + public void clear_unregistersAllPlotListeners() { + Plot plot = new MockPlot("MockPlot"); + plot.addSeries(new MockSeries(), new MockFormatter1()); + plot.addSeries(new MockSeries(), new MockFormatter1()); + plot.addSeries(new MockSeries(), new MockFormatter1()); + assertEquals(3, plot.getListeners().size()); + + plot.clear(); + assertEquals(0, plot.getListeners().size()); + } + + @Test + public void clear_clearsRegistry() { + Plot plot = new MockPlot("MockPlot"); + plot.setRegistry(mockSeriesRegistry); + + plot.clear(); + verify(mockSeriesRegistry).clear(); + } + + @Test + public void setPlotMargins_updatesMargins() { + Plot plot = new MockPlot("MockPlot"); + plot.setPlotMargins(11, 22, 33, 44); + + assertEquals(11f, plot.getPlotMarginLeft()); + assertEquals(22f, plot.getPlotMarginTop()); + assertEquals(33f, plot.getPlotMarginRight()); + assertEquals(44f, plot.getPlotMarginBottom()); + } + + @Test + public void setPlotPadding_updatesPadding() { + Plot plot = new MockPlot("MockPlot"); + plot.setPlotPadding(11, 22, 33, 44); + + assertEquals(11f, plot.getPlotPaddingLeft()); + assertEquals(22f, plot.getPlotPaddingTop()); + assertEquals(33f, plot.getPlotPaddingRight()); + assertEquals(44f, plot.getPlotPaddingBottom()); + } + + static class MockPlotListener implements PlotListener { + + public void onBeforeDraw(Plot source, Canvas canvas) { + } + + public void onAfterDraw(Plot source, Canvas canvas) { + } + } + + static class MockSeries implements Series, PlotListener { + + public String getTitle() { + return null; + } + + @Override + public void onBeforeDraw(Plot source, Canvas canvas) { + + } + + @Override + public void onAfterDraw(Plot source, Canvas canvas) { + + } + } + + static class MockSeries2 implements Series { + + public String getTitle() { + return null; + } + } + + static class MockSeries3 implements Series { + + public String getTitle() { + return null; + } + } + + static class MockRenderer1 extends SeriesRenderer { + + public MockRenderer1(Plot plot) { + super(plot); + } + + @Override + public void onRender(Canvas canvas, RectF plotArea, Series series, Formatter formatter, RenderStack stack) throws PlotRenderException { + + } + + @Override + public void doDrawLegendIcon(Canvas canvas, RectF rect, Formatter formatter) { + + } + } + + static class MockRenderer2 extends SeriesRenderer { + + public MockRenderer2(Plot plot) { + super(plot); + } + + @Override + public void onRender(Canvas canvas, RectF plotArea, Series series, Formatter formatter, RenderStack stack) throws PlotRenderException { + + } + + @Override + public void doDrawLegendIcon(Canvas canvas, RectF rect, Formatter formatter) { + + } + } + + static class MockFormatter1 extends Formatter { + + @Override + public Class getRendererClass() { + return MockRenderer1.class; + } + + @Override + public SeriesRenderer doGetRendererInstance(MockPlot plot) { + return new MockRenderer1(plot); + } + } + + static class MockFormatter2 extends Formatter { + + @Override + public Class getRendererClass() { + return MockRenderer2.class; + } + + @Override + public SeriesRenderer doGetRendererInstance(MockPlot plot) { + return new MockRenderer2(plot); + } + } + + public static class MockSeriesBundle extends SeriesBundle { + + public MockSeriesBundle(MockSeries series, Formatter formatter) { + super(series, formatter); + } + } + + public static class MockPlot extends Plot> { + public MockPlot(String title) { + super(RuntimeEnvironment.application, title); + } + + @Override + protected SeriesRegistry getRegistryInstance() { + return new SeriesRegistry() { + @Override + protected MockSeriesBundle newSeriesBundle( + MockSeries series, Formatter formatter) { + return new MockSeriesBundle(series, formatter); + } + }; + } + + @Override + protected void onPreInit() { + + } + + @Override + protected void processAttrs(TypedArray attrs) { + + } + } } diff --git a/androidplot-core/src/test/java/com/androidplot/ui/widget/TextLabelWidgetTest.java b/androidplot-core/src/test/java/com/androidplot/ui/widget/TextLabelWidgetTest.java new file mode 100644 index 00000000..6ed81b7b --- /dev/null +++ b/androidplot-core/src/test/java/com/androidplot/ui/widget/TextLabelWidgetTest.java @@ -0,0 +1,82 @@ +package com.androidplot.ui.widget; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; + +import com.androidplot.test.AndroidplotTest; +import com.androidplot.ui.LayoutManager; +import com.androidplot.ui.Size; +import com.androidplot.ui.TextOrientation; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mock; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyFloat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class TextLabelWidgetTest extends AndroidplotTest { + + @Mock + LayoutManager layoutManager; + + @Mock + Size size; + + @Mock + Canvas canvas; + + @Mock + RectF rectF; + + private TextLabelWidget textLabelWidget; + + @Before + public void before() { + textLabelWidget = spy(new TextLabelWidget(layoutManager, size)); + } + + @Test + public void onMetricsChanged_invokesPack_ifAutopackEnabled() { + textLabelWidget.setAutoPackEnabled(true); + textLabelWidget.onMetricsChanged(size, size); + verify(textLabelWidget, times(2)).pack(); + } + + @Test + public void onMetricsChanged_doesNotInvokePack_ifAutopackDisabled() { + textLabelWidget.setAutoPackEnabled(false); + textLabelWidget.onMetricsChanged(size, size); + verify(textLabelWidget, never()).pack(); + } + + @Test + public void doOnDraw_rotatesThenDraws_ifVerticalAscending() { + textLabelWidget.setText("this is a test"); + textLabelWidget.setOrientation(TextOrientation.VERTICAL_ASCENDING); + textLabelWidget.doOnDraw(canvas, rectF); + + InOrder inOrder = inOrder(canvas); + inOrder.verify(canvas).rotate(-90); + inOrder.verify(canvas).drawText(eq(textLabelWidget.getText()), anyFloat(), anyFloat(), any(Paint.class)); + } + + @Test + public void doOnDraw_rotatesThenDraws_ifVerticalDescending() { + textLabelWidget.setText("this is a test"); + textLabelWidget.setOrientation(TextOrientation.VERTICAL_DESCENDING); + textLabelWidget.doOnDraw(canvas, rectF); + + InOrder inOrder = inOrder(canvas); + inOrder.verify(canvas).rotate(90); + inOrder.verify(canvas).drawText(eq(textLabelWidget.getText()), anyFloat(), anyFloat(), any(Paint.class)); + } +} diff --git a/androidplot-core/src/test/java/com/androidplot/util/LayerHashTest.java b/androidplot-core/src/test/java/com/androidplot/util/LayerHashTest.java index 3d479495..3dbd6970 100644 --- a/androidplot-core/src/test/java/com/androidplot/util/LayerHashTest.java +++ b/androidplot-core/src/test/java/com/androidplot/util/LayerHashTest.java @@ -35,33 +35,32 @@ public void setUp() throws Exception { layerHash.addToBottom(obj2, obj2); layerHash.addToBottom(obj3, obj3); - assertEquals(obj3, layerHash.getKeysAsList().get(0)); - assertEquals(obj2, layerHash.getKeysAsList().get(1)); - assertEquals(obj1, layerHash.getKeysAsList().get(2)); + assertEquals(obj1, layerHash.getTop()); + assertEquals(obj3, layerHash.getBottom()); } // "top" == last element in the list: @Test - public void testMoveUp() throws Exception { + public void moveUp() { layerHash.moveUp(obj3); - assertEquals(obj2, layerHash.getKeysAsList().get(0)); - assertEquals(obj3, layerHash.getKeysAsList().get(1)); - assertEquals(obj1, layerHash.getKeysAsList().get(2)); + assertEquals(obj1, layerHash.getTop()); + assertEquals(obj3, layerHash.getBeneath(obj1)); + assertEquals(obj2, layerHash.getBeneath(obj3)); layerHash.moveUp(obj3); - assertEquals(obj2, layerHash.getKeysAsList().get(0)); - assertEquals(obj1, layerHash.getKeysAsList().get(1)); - assertEquals(obj3, layerHash.getKeysAsList().get(2)); + assertEquals(obj3, layerHash.getTop()); + assertEquals(obj1, layerHash.getBeneath(obj3)); + assertEquals(obj2, layerHash.getBeneath(obj1)); layerHash.moveUp(obj3); - assertEquals(obj2, layerHash.getKeysAsList().get(0)); - assertEquals(obj1, layerHash.getKeysAsList().get(1)); - assertEquals(obj3, layerHash.getKeysAsList().get(2)); + assertEquals(obj3, layerHash.getTop()); + assertEquals(obj1, layerHash.getBeneath(obj3)); + assertEquals(obj2, layerHash.getBeneath(obj1)); } // "bottom" == first element in the list: @Test - public void testMoveDown() throws Exception { + public void moveDown() { layerHash.moveDown(obj1); assertEquals(obj3, layerHash.getKeysAsList().get(0)); assertEquals(obj1, layerHash.getKeysAsList().get(1)); @@ -79,7 +78,7 @@ public void testMoveDown() throws Exception { } @Test - public void testMoveAbove() throws Exception { + public void moveAbove() { layerHash.moveAbove(obj2, obj1); assertEquals(obj3, layerHash.getKeysAsList().get(0)); assertEquals(obj1, layerHash.getKeysAsList().get(1)); @@ -87,11 +86,45 @@ public void testMoveAbove() throws Exception { } @Test - public void testMoveBeneath() throws Exception { + public void moveBeneath() { layerHash.moveBeneath(obj1, obj2); assertEquals(obj3, layerHash.getKeysAsList().get(0)); assertEquals(obj1, layerHash.getKeysAsList().get(1)); assertEquals(obj2, layerHash.getKeysAsList().get(2)); } + @Test + public void addToTop() { + Object obj = new Object(); + layerHash.addToTop(obj, obj); + assertEquals(obj, layerHash.getTop()); + assertEquals(obj3, layerHash.getKeysAsList().get(0)); + assertEquals(obj2, layerHash.getKeysAsList().get(1)); + assertEquals(obj1, layerHash.getKeysAsList().get(2)); + assertEquals(obj, layerHash.getKeysAsList().get(3)); + } + + @Test + public void moveToTop() { + layerHash.moveToTop(obj3); + assertEquals(obj3, layerHash.getTop()); + assertEquals(obj1, layerHash.getBeneath(obj3)); + assertEquals(obj2, layerHash.getBeneath(obj1)); + } + + @Test + public void moveToBottom() { + layerHash.moveToBottom(obj1); + assertEquals(obj1, layerHash.getBottom()); + assertEquals(obj3, layerHash.getAbove(obj1)); + assertEquals(obj2, layerHash.getAbove(obj3)); + } + + @Test + public void remove() { + layerHash.remove(obj2); + assertEquals(2, layerHash.size()); + assertEquals(obj1, layerHash.getAbove(obj3)); + } + } diff --git a/androidplot-core/src/test/java/com/androidplot/util/PlotStatisticsTest.java b/androidplot-core/src/test/java/com/androidplot/util/PlotStatisticsTest.java new file mode 100644 index 00000000..b063235a --- /dev/null +++ b/androidplot-core/src/test/java/com/androidplot/util/PlotStatisticsTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2015 AndroidPlot.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.androidplot.util; + +import android.graphics.Canvas; +import android.graphics.Paint; + +import com.androidplot.Plot; +import com.androidplot.test.AndroidplotTest; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyFloat; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class PlotStatisticsTest extends AndroidplotTest { + + @Mock + Canvas canvas; + + @Mock + Paint paint; + + @Mock + Plot plot; + + @InjectMocks + PlotStatistics ps = new PlotStatistics(1, true); + + @Before + public void before() { + when(plot.getDisplayDimensions()).thenReturn(new DisplayDimensions()); + } + + @Test + public void annotatePlot_annotates_ifEnabled() { + ps.onAfterDraw(plot, canvas); + verify(canvas).drawText(anyString(), anyFloat(), anyFloat(), any(Paint.class)); + } + + @Test + public void annotatePlot_doesNotAnnotate_ifDisabled() { + ps.setEnabled(false); + ps.onAfterDraw(plot, canvas); + verify(canvas, never()).drawText(anyString(), anyFloat(), anyFloat(), any(Paint.class)); + } +} diff --git a/androidplot-core/src/test/java/com/androidplot/util/SeriesUtilsTest.java b/androidplot-core/src/test/java/com/androidplot/util/SeriesUtilsTest.java index 83a7eb81..4ff129bc 100644 --- a/androidplot-core/src/test/java/com/androidplot/util/SeriesUtilsTest.java +++ b/androidplot-core/src/test/java/com/androidplot/util/SeriesUtilsTest.java @@ -27,6 +27,7 @@ import static junit.framework.Assert.assertNull; import static junit.framework.Assert.fail; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class SeriesUtilsTest { @@ -101,6 +102,13 @@ public void testSeriesMinMax() { assertEquals(null, minMax.getMaxY()); } + @Test + public void minMax_usesSeriesMinMax_onFastXYSeries() { + FastXYSeries series = mock(FastXYSeries.class); + SeriesUtils.minMax(series); + verify(series).minMax(); + } + @Test public void testListMinMax() { Region minMax = SeriesUtils.minMax(LINEAR); diff --git a/androidplot-core/src/test/java/com/androidplot/xy/LineAndPointRendererTest.java b/androidplot-core/src/test/java/com/androidplot/xy/LineAndPointRendererTest.java index ef67bb36..9cb03cd1 100644 --- a/androidplot-core/src/test/java/com/androidplot/xy/LineAndPointRendererTest.java +++ b/androidplot-core/src/test/java/com/androidplot/xy/LineAndPointRendererTest.java @@ -217,4 +217,46 @@ public void testCullPointsCache() throws Exception { renderer.cullPointsCache(); assertEquals(0, renderer.pointsCaches.size()); } + + @Test + public void renderPath_rendersRegions() { + LineAndPointFormatter formatter = + new LineAndPointFormatter(0, 0, 0, null); + + XYRegionFormatter r1 = new XYRegionFormatter(Color.RED); + formatter.addRegion(new RectRegion(0, 2, 0, 2, "region1"), r1); + + XYRegionFormatter r2 = new XYRegionFormatter(Color.GREEN); + formatter.addRegion(new RectRegion(0, 2, 0, 2, "region2"), r2); + + SimpleXYSeries series = new SimpleXYSeries( + SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "some data", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + + xyPlot.addSeries(series, formatter); + LineAndPointRenderer renderer = xyPlot.getRenderer(LineAndPointRenderer.class); + + //xyPlot.draw(canvas); + renderer.renderPath(canvas, plotArea, new Path(), mock(PointF.class), mock(PointF.class), formatter); + verify(canvas).drawRect(any(RectF.class), eq(r1.getPaint())); + verify(canvas).drawRect(any(RectF.class), eq(r2.getPaint())); + } + + @Test + public void drawSeries_withPointLabelFormatter_drawsPointLabels() { + LineAndPointFormatter formatter = + new LineAndPointFormatter(0, 0, 0, null); + formatter.setPointLabelFormatter(new PointLabelFormatter(Color.RED)); + SimpleXYSeries series = new SimpleXYSeries( + SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "some data", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + xyPlot.addSeries(series, formatter); + LineAndPointRenderer renderer = xyPlot.getRenderer(LineAndPointRenderer.class); + renderer.drawSeries(canvas, plotArea, series, formatter); + + verify(canvas, times(series.size())).drawText( + anyString(), + anyFloat(), + anyFloat(), + eq(formatter.getPointLabelFormatter().getTextPaint())); + + } } diff --git a/androidplot-core/src/test/java/com/androidplot/xy/RectRegionTest.java b/androidplot-core/src/test/java/com/androidplot/xy/RectRegionTest.java index 70a9bbe1..c6e11ae7 100644 --- a/androidplot-core/src/test/java/com/androidplot/xy/RectRegionTest.java +++ b/androidplot-core/src/test/java/com/androidplot/xy/RectRegionTest.java @@ -21,7 +21,6 @@ import com.androidplot.test.AndroidplotTest; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import static junit.framework.Assert.assertEquals; @@ -196,55 +195,4 @@ public void testUnion() throws Exception { assertEquals(0, r2.getMinY().doubleValue(), 0); assertEquals(200, r2.getMaxY().doubleValue(), 0); } - - /** - * Currently ignored as the base implementation does not currently pass. - */ - @Ignore - @Test - public void testOverlapsLine() { - RectRegion r1 = new RectRegion(0, 100, 0, 100); - -// assertFalse(r1.intersectsWithLine(200, 200, 400, 400)); -// assertFalse(r1.intersectsWithLine(-1, -1, -1, 100)); -// assertTrue(r1.intersectsWithLine(0, 0, 100, 100)); -// assertTrue(r1.intersectsWithLine(50, 50, 200, 200)); - - // lines running parallel to region edges: - assertTrue(r1.intersectsWithLine(0, 0, 0, 100)); - assertTrue(r1.intersectsWithLine(0, 0, 100, 0)); - assertTrue(r1.intersectsWithLine(0, 100, 0, 0)); - assertTrue(r1.intersectsWithLine(100, 0, 0, 0)); - - assertTrue(r1.intersectsWithLine(100, 0, 0, 0)); - assertTrue(r1.intersectsWithLine(0, 100, 0, 0)); - assertTrue(r1.intersectsWithLine(0, 0, 100, 0)); - assertTrue(r1.intersectsWithLine(0, 0, 0, 100)); - - // lines passing through top & bottom edges only: - assertTrue(r1.intersectsWithLine(50, -1000, 50, 1000)); - assertTrue(r1.intersectsWithLine(50, 1000, 50, -1000)); - - // lines passing through left & right edges only: - assertTrue(r1.intersectsWithLine(-1000, 50, 1000, 50)); - assertTrue(r1.intersectsWithLine(1000, 50, -100, 50)); - - // diagonal passing through bottom-left and top-right corners: - assertTrue(r1.intersectsWithLine(-100, -100, 200, 200)); - - // diagonal passing inside upper-left edge - assertTrue(r1.intersectsWithLine(-20, 80, 20, 120)); - assertTrue(r1.intersectsWithLine(20, 120, -20, 80)); - - // diagonal passing outside upper-left edge - - // diagonal passing inside lower-left edge - // diagonal passing outside lower-left edge - - // diagonal passing inside upper-right edge - // diagonal passing outside upper-right edge - - // diagonal passing inside lower-right edge - // diagonal passing outside lower-right edge - } } diff --git a/androidplot-core/src/test/java/com/androidplot/xy/SimpleXYSeriesTest.java b/androidplot-core/src/test/java/com/androidplot/xy/SimpleXYSeriesTest.java index 397e7700..50fd0f8a 100644 --- a/androidplot-core/src/test/java/com/androidplot/xy/SimpleXYSeriesTest.java +++ b/androidplot-core/src/test/java/com/androidplot/xy/SimpleXYSeriesTest.java @@ -19,6 +19,7 @@ import org.junit.Test; import java.util.Arrays; +import java.util.NoSuchElementException; import static junit.framework.Assert.assertEquals; @@ -146,4 +147,43 @@ public void testResize() throws Exception { assertEquals(0, series.size()); } + @Test + public void setXY_setsXAndY() { + SimpleXYSeries series = new SimpleXYSeries("series"); + series.resize(5); + series.setXY(100, 200, 0); + + assertEquals(100, series.getX(0)); + assertEquals(200, series.getY(0)); + + } + + @Test(expected = NoSuchElementException.class) + public void removeFirst_throwsNoSuchElementException_ifEmpty() { + new SimpleXYSeries("series").removeFirst(); + } + + @Test(expected = NoSuchElementException.class) + public void removeLast_throwsNoSuchElementException_ifEmpty() { + new SimpleXYSeries("series").removeLast(); + } + + @Test + public void setTitle_changesTitle() { + SimpleXYSeries series = new SimpleXYSeries("series"); + + final String newTitle = "newTitle"; + series.setTitle(newTitle); + assertEquals(newTitle, series.getTitle()); + } + + @Test + public void clear_removesEverything() { + SimpleXYSeries series = new SimpleXYSeries( + Arrays.asList(1, 2, 3, 4, 5), + SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "series"); + + series.clear(); + assertEquals(0, series.size()); + } } diff --git a/androidplot-core/src/test/java/com/androidplot/xy/XYPlotTest.java b/androidplot-core/src/test/java/com/androidplot/xy/XYPlotTest.java index 553314a4..2e8c6094 100644 --- a/androidplot-core/src/test/java/com/androidplot/xy/XYPlotTest.java +++ b/androidplot-core/src/test/java/com/androidplot/xy/XYPlotTest.java @@ -31,6 +31,7 @@ import java.util.List; import static junit.framework.Assert.assertEquals; +import static org.mockito.Mockito.mock; public class XYPlotTest extends AndroidplotTest { @@ -38,7 +39,7 @@ public class XYPlotTest extends AndroidplotTest { List numList1; List numList2; - SimpleXYSeries series1; + SimpleXYSeries series0To100; @Before public void setUp() throws Exception { @@ -46,7 +47,7 @@ public void setUp() throws Exception { plot = new XYPlot(getContext(), "test"); numList1 = Arrays.asList(0, 1, 3, 5, 10, 15, 25, 50, 75, 100); // 10 elements numList2 = Arrays.asList(-100, 0, 1, 3, 5, 10, 15, 25, 50, 75, 100, 200); // 12 elements - series1 = new SimpleXYSeries(numList1, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, ""); + series0To100 = new SimpleXYSeries(numList1, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, ""); } @After @@ -56,7 +57,7 @@ public void tearDown() throws Exception { @Test public void testOriginFixedMode() throws Exception { - plot.addSeries(series1, new LineAndPointFormatter()); + plot.addSeries(series0To100, new LineAndPointFormatter()); plot.centerOnDomainOrigin(5, 2, BoundaryMode.FIXED); plot.calculateMinMaxVals(); @@ -67,7 +68,7 @@ public void testOriginFixedMode() throws Exception { @Test public void testOriginAutoMode() throws Exception { - plot.addSeries(series1, new LineAndPointFormatter()); + plot.addSeries(series0To100, new LineAndPointFormatter()); plot.centerOnDomainOrigin(5); plot.calculateMinMaxVals(); @@ -84,7 +85,7 @@ public void testOriginAutoMode() throws Exception { @Test public void testOriginGrowMode() throws Exception { - plot.addSeries(series1, new LineAndPointFormatter()); + plot.addSeries(series0To100, new LineAndPointFormatter()); plot.centerOnDomainOrigin(5, null, BoundaryMode.GROW); plot.calculateMinMaxVals(); @@ -92,14 +93,14 @@ public void testOriginGrowMode() throws Exception { assertEquals(10.0, plot.getBounds().getMaxX().doubleValue(), 0); // introduce a larger domain set. boundaries should change - series1.setModel(numList2, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); + series0To100.setModel(numList2, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); plot.calculateMinMaxVals(); assertEquals(-1.0, plot.getBounds().getMinX().doubleValue(), 0); assertEquals(11.0, plot.getBounds().getMaxX().doubleValue(), 0); // revert series model back to the previous set. boundaries should remain the same - series1.setModel(numList1, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); + series0To100.setModel(numList1, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); plot.calculateMinMaxVals(); assertEquals(-1.0, plot.getBounds().getMinX().doubleValue(), 0); @@ -108,7 +109,7 @@ public void testOriginGrowMode() throws Exception { @Test public void testOriginShrinkMode() throws Exception { - plot.addSeries(series1, new LineAndPointFormatter()); + plot.addSeries(series0To100, new LineAndPointFormatter()); plot.centerOnDomainOrigin(5, null, BoundaryMode.SHRINK); plot.calculateMinMaxVals(); @@ -116,18 +117,27 @@ public void testOriginShrinkMode() throws Exception { assertEquals(10.0, plot.getBounds().getMaxX().doubleValue(), 0); // update with more extreme values...nothing should change in shrink mode: - series1.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); + series0To100.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); assertEquals(0.0, plot.getBounds().getMinX().doubleValue(), 0); assertEquals(10.0, plot.getBounds().getMaxX().doubleValue(), 0); } + @Test(expected = IllegalArgumentException.class) + public void centerOnRangeOrigin_throwsIllegalArgumentException_ifNullOrigin() { + plot.centerOnRangeOrigin(null); + } + + @Test(expected = IllegalArgumentException.class) + public void centerOnDomainOrigin_throwsIllegalArgumentException_ifNullOrigin() { + plot.centerOnDomainOrigin(null); + } // Ifor not sure about filling in test stubs just going to do my own stuff instead. @Test public void testsetDomainBoundaries() throws Exception { - plot.addSeries(series1, new LineAndPointFormatter()); + plot.addSeries(series0To100, new LineAndPointFormatter()); plot.calculateMinMaxVals(); // default to auto so run them @@ -159,7 +169,7 @@ public void testsetDomainBoundaries() throws Exception { assertEquals(9, plot.getBounds().getMaxX().doubleValue(), 0); // update with more extreme values... - series1.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); + series0To100.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); plot.calculateMinMaxVals(); // after growing @@ -167,7 +177,7 @@ public void testsetDomainBoundaries() throws Exception { assertEquals(11, plot.getBounds().getMaxX().doubleValue(), 0); // back to previous - series1.setModel(numList1,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); + series0To100.setModel(numList1,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); plot.calculateMinMaxVals(); // should not of changed. @@ -175,7 +185,7 @@ public void testsetDomainBoundaries() throws Exception { assertEquals(11, plot.getBounds().getMaxX().doubleValue(), 0); // back to big - series1.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); + series0To100.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); plot.setDomainBoundaries(2, BoundaryMode.SHRINK, 8, BoundaryMode.SHRINK); plot.calculateMinMaxVals(); @@ -185,7 +195,7 @@ public void testsetDomainBoundaries() throws Exception { assertEquals(11, plot.getBounds().getMaxX().doubleValue(), 0); // now small - series1.setModel(numList1,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); + series0To100.setModel(numList1,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); plot.calculateMinMaxVals(); // after shrinking @@ -193,7 +203,7 @@ public void testsetDomainBoundaries() throws Exception { assertEquals(9, plot.getBounds().getMaxX().doubleValue(), 0); // back to previous - series1.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); + series0To100.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); plot.calculateMinMaxVals(); // should not of changed. @@ -210,8 +220,8 @@ public void testsetDomainBoundaries() throws Exception { } @Test - public void testsetRangeBoundaries() throws Exception { - plot.addSeries(series1, new LineAndPointFormatter()); + public void setRangeBoundaries_calculatesCorrectMinMaxVals() throws Exception { + plot.addSeries(series0To100, new LineAndPointFormatter()); plot.calculateMinMaxVals(); // default to auto so run them @@ -242,7 +252,7 @@ public void testsetRangeBoundaries() throws Exception { assertEquals(100, plot.getBounds().getMaxY().doubleValue(), 0); // update with more extreme values... - series1.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); + series0To100.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); plot.calculateMinMaxVals(); // after growing @@ -250,7 +260,7 @@ public void testsetRangeBoundaries() throws Exception { assertEquals(200, plot.getBounds().getMaxY().doubleValue(), 0); // back to previous - series1.setModel(numList1,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); + series0To100.setModel(numList1,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); plot.calculateMinMaxVals(); // should not of changed. @@ -258,7 +268,7 @@ public void testsetRangeBoundaries() throws Exception { assertEquals(200, plot.getBounds().getMaxY().doubleValue(), 0); // back to big - series1.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); + series0To100.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); plot.setRangeBoundaries(2, BoundaryMode.SHRINK, 8, BoundaryMode.SHRINK); plot.calculateMinMaxVals(); @@ -268,7 +278,7 @@ public void testsetRangeBoundaries() throws Exception { assertEquals(200, plot.getBounds().getMaxY().doubleValue(), 0); // now small - series1.setModel(numList1,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); + series0To100.setModel(numList1,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); plot.calculateMinMaxVals(); // after shrinking @@ -276,7 +286,7 @@ public void testsetRangeBoundaries() throws Exception { assertEquals(100, plot.getBounds().getMaxY().doubleValue(), 0); // back to previous - series1.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); + series0To100.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); plot.calculateMinMaxVals(); // should not of changed. @@ -294,7 +304,7 @@ public void testsetRangeBoundaries() throws Exception { @Test public void testSetDomainRightMinMax() throws Exception { - plot.addSeries(series1, new LineAndPointFormatter()); + plot.addSeries(series0To100, new LineAndPointFormatter()); plot.calculateMinMaxVals(); // default to auto so run them @@ -308,7 +318,7 @@ public void testSetDomainRightMinMax() throws Exception { assertEquals(0, plot.getBounds().getMinX().doubleValue(), 0); assertEquals(9, plot.getBounds().getMaxX().doubleValue(), 0); - series1.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); + series0To100.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); plot.calculateMinMaxVals(); // on RightMax @@ -331,7 +341,7 @@ public void testSetDomainRightMinMax() throws Exception { assertEquals(11, plot.getBounds().getMaxX().doubleValue(), 0); // small list - series1.setModel(numList1,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); + series0To100.setModel(numList1,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); plot.calculateMinMaxVals(); // on RightMin @@ -349,7 +359,7 @@ public void testSetDomainRightMinMax() throws Exception { @Test public void testSetRangeTopBottomMinMax() throws Exception { - plot.addSeries(series1, new LineAndPointFormatter()); + plot.addSeries(series0To100, new LineAndPointFormatter()); plot.calculateMinMaxVals(); // default to auto so run them @@ -364,7 +374,7 @@ public void testSetRangeTopBottomMinMax() throws Exception { assertEquals(0, plot.getBounds().getMinY().doubleValue(), 0); assertEquals(100, plot.getBounds().getMaxY().doubleValue(), 0); - series1.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); + series0To100.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); plot.calculateMinMaxVals(); // on Limits @@ -389,7 +399,7 @@ public void testSetRangeTopBottomMinMax() throws Exception { assertEquals(200, plot.getBounds().getMaxY().doubleValue(), 0); // small list - series1.setModel(numList1,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); + series0To100.setModel(numList1,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); plot.calculateMinMaxVals(); // on Limits @@ -407,33 +417,45 @@ public void testSetRangeTopBottomMinMax() throws Exception { } @Test - public void testSetDomainUpperBoundary() throws Exception { - + public void setDomainUpperBoundary_overridesCalculatedBoundary() throws Exception { + plot.addSeries(series0To100, new LineAndPointFormatter()); + plot.setDomainUpperBoundary(350, BoundaryMode.FIXED); + plot.calculateMinMaxVals(); + assertEquals(350, plot.getBounds().getMaxX().intValue()); } @Test public void testSetDomainLowerBoundary() throws Exception { - + plot.addSeries(series0To100, new LineAndPointFormatter()); + plot.setDomainLowerBoundary(-350, BoundaryMode.FIXED); + plot.calculateMinMaxVals(); + assertEquals(-350, plot.getBounds().getMinX().intValue()); } @Test public void testSetRangeUpperBoundary() throws Exception { - + plot.addSeries(series0To100, new LineAndPointFormatter()); + plot.setRangeUpperBoundary(350, BoundaryMode.FIXED); + plot.calculateMinMaxVals(); + assertEquals(350, plot.getBounds().getMaxY().intValue()); } @Test public void testSetRangeLowerBoundary() throws Exception { - + plot.addSeries(series0To100, new LineAndPointFormatter()); + plot.setRangeLowerBoundary(-350, BoundaryMode.FIXED); + plot.calculateMinMaxVals(); + assertEquals(-350, plot.getBounds().getMinY().intValue()); } @Test public void testSetDomainOrigin() throws Exception { - + // TODO } @Test public void testSetRangeOrigin() throws Exception { - + // TODO } @Test @@ -451,4 +473,81 @@ public void testConfigure() throws Exception { assertEquals(Plot.RenderMode.USE_BACKGROUND_THREAD, plot.getRenderMode()); assertEquals(Color.parseColor(param3), plot.getBackgroundPaint().getColor()); } + + @Test + public void removeMarker_withXMarker_removesExpectedXMarkerOnly() { + + XValueMarker xMarker1 = new XValueMarker(1, "x1"); + XValueMarker xMarker2 = new XValueMarker(2, "x2"); + XValueMarker xMarker3 = new XValueMarker(2, "x2"); + XValueMarker xMarker4 = new XValueMarker(2, "x2"); + XValueMarker xMarker5 = new XValueMarker(2, "x2"); + + plot.addMarker(xMarker1); + plot.addMarker(xMarker2); + plot.addMarker(xMarker3); + plot.addMarker(xMarker4); + plot.addMarker(xMarker5); + + assertEquals(5, plot.getXValueMarkers().size()); + + assertEquals(xMarker3, plot.removeMarker(xMarker3)); + assertEquals(4, plot.getXValueMarkers().size()); + } + + @Test + public void removeMarker_withYMarker_removesExpectedYMarkerOnly() { + + YValueMarker YMarker1 = new YValueMarker(1, "Y1"); + YValueMarker YMarker2 = new YValueMarker(2, "Y2"); + YValueMarker YMarker3 = new YValueMarker(2, "Y2"); + YValueMarker YMarker4 = new YValueMarker(2, "Y2"); + YValueMarker YMarker5 = new YValueMarker(2, "Y2"); + + plot.addMarker(YMarker1); + plot.addMarker(YMarker2); + plot.addMarker(YMarker3); + plot.addMarker(YMarker4); + plot.addMarker(YMarker5); + + assertEquals(5, plot.getYValueMarkers().size()); + + assertEquals(YMarker3, plot.removeMarker(YMarker3)); + assertEquals(4, plot.getYValueMarkers().size()); + } + + @Test + public void removeMarkers_removesAllXAndYMarkers() { + + XValueMarker xMarker1 = new XValueMarker(1, "x1"); + XValueMarker xMarker2 = new XValueMarker(2, "x2"); + XValueMarker xMarker3 = new XValueMarker(2, "x2"); + XValueMarker xMarker4 = new XValueMarker(2, "x2"); + XValueMarker xMarker5 = new XValueMarker(2, "x2"); + + plot.addMarker(xMarker1); + plot.addMarker(xMarker2); + plot.addMarker(xMarker3); + plot.addMarker(xMarker4); + plot.addMarker(xMarker5); + + YValueMarker YMarker1 = new YValueMarker(1, "Y1"); + YValueMarker YMarker2 = new YValueMarker(2, "Y2"); + YValueMarker YMarker3 = new YValueMarker(2, "Y2"); + YValueMarker YMarker4 = new YValueMarker(2, "Y2"); + YValueMarker YMarker5 = new YValueMarker(2, "Y2"); + + plot.addMarker(YMarker1); + plot.addMarker(YMarker2); + plot.addMarker(YMarker3); + plot.addMarker(YMarker4); + plot.addMarker(YMarker5); + + assertEquals(5, plot.getXValueMarkers().size()); + assertEquals(5, plot.getYValueMarkers().size()); + + plot.removeMarkers(); + assertEquals(0, plot.getXValueMarkers().size()); + assertEquals(0, plot.getYValueMarkers().size()); + } } diff --git a/build.gradle b/build.gradle index 11e85b40..973ff339 100644 --- a/build.gradle +++ b/build.gradle @@ -23,9 +23,9 @@ allprojects { } ext { - theBuildToolsVersion = '24.0.2' - theCompileSdkVersion = 24 - theTargetSdkVersion = 24 + theBuildToolsVersion = '25.0.2' + theCompileSdkVersion = 25 + theTargetSdkVersion = 25 theMinSdkVersion = 5 theVersionName = '1.4.3' theVersionCode = 0 @@ -38,7 +38,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.3.0' + classpath 'com.android.tools.build:gradle:2.3.1' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7' classpath 'com.vanniktech:gradle-android-junit-jacoco-plugin:0.5.0' diff --git a/circle.yml b/circle.yml index baf2d393..494b4bc2 100644 --- a/circle.yml +++ b/circle.yml @@ -7,8 +7,8 @@ machine: dependencies: pre: - - echo y | android update sdk --no-ui --all --filter "android-24" - - echo y | android update sdk --no-ui --all --filter "build-tools-24.0.2" + - if [ ! -e /usr/local/android-sdk-linux/platforms/android-25 ]; then echo y | android update sdk --all --no-ui --filter "android-25"; fi; + - if [ ! -e /usr/local/android-sdk-linux/build-tools/25.0.2 ]; then echo y | android update sdk --all --no-ui --filter "build-tools-25.0.2"; fi; - bash ./misc/download_keystore.sh - bash ./misc/inject_circle_build_number.sh diff --git a/demoapp-wearable/build.gradle b/demoapp-wearable/build.gradle index 75997b20..dd1d8926 100644 --- a/demoapp-wearable/build.gradle +++ b/demoapp-wearable/build.gradle @@ -19,7 +19,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.0' + classpath 'com.android.tools.build:gradle:2.3.1' } } apply plugin: 'com.android.application' From ab1ee7d87e12b7cb3d67a9d9a2c1e3faf09ff946 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Sat, 29 Apr 2017 00:32:26 -0500 Subject: [PATCH 17/81] Update README.md corrected codix link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index de4e8522..0e4e127d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ compatible with all versions of Android from 1.6 onward and is **used by over [![Codix](http://codix.io/gh/badge/halfhp/androidplot)](http://codix.io/gh/repo/halfhp/androidplot) -If you enjoy the lib, please [rate us on codix.io](http://codix.io/repo/halfhp/androidplot)! +If you enjoy the lib, please [rate us on codix.io](http://codix.io/gh/repo/halfhp/androidplot)! From 271438c0b76867f7260925c50bd1188b60696709 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Tue, 9 May 2017 22:12:14 -0400 Subject: [PATCH 18/81] #39 - FastLineAndPointRenderer will not render vertices in legend items. (#40) --- .../java/com/androidplot/xy/FastLineAndPointRenderer.java | 4 ++++ docs/release_notes.md | 3 +++ 2 files changed, 7 insertions(+) diff --git a/androidplot-core/src/main/java/com/androidplot/xy/FastLineAndPointRenderer.java b/androidplot-core/src/main/java/com/androidplot/xy/FastLineAndPointRenderer.java index e5aa7bc9..7aaa58a8 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/FastLineAndPointRenderer.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/FastLineAndPointRenderer.java @@ -116,6 +116,10 @@ protected void doDrawLegendIcon(Canvas canvas, RectF rect, Formatter formatter) if(formatter.hasLinePaint()) { canvas.drawLine(rect.left, rect.bottom, rect.right, rect.top, formatter.getLinePaint()); } + + if(formatter.hasVertexPaint()) { + canvas.drawPoint(rect.centerX(), rect.centerY(), formatter.getVertexPaint()); + } } /** diff --git a/docs/release_notes.md b/docs/release_notes.md index 995cfbbc..24491271 100644 --- a/docs/release_notes.md +++ b/docs/release_notes.md @@ -2,6 +2,9 @@ For details on what to expect in general when updating to a new version of Androiplot, check out the [versioning doc](versioning.md). +# 1.4.3 +* (#39) `FastLineAndPointRenderer` now renders vertices for legend items. + # 1.4.2 * (#32) New step mode: `INCREMENT_BY_FIT`. * (#33) PanZoom support for 'INCREMENT_BY_FIT'. From 95ca00ed2826e684d6b5758cc4bf35e9f566538d Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Fri, 12 May 2017 07:22:38 -0400 Subject: [PATCH 19/81] XML Attrs Documentation / Generator (#41) * initial implimentation of the xml attrs documentation generator with basic documentation. * more xml attr documentation. --- androidplot-core/build.gradle | 36 + .../java/com/androidplot/util/AttrUtils.java | 2 +- .../src/main/res/values/attrs.xml | 753 ++++++++++++++++-- docs/attrs.md | 556 +++++++++++++ docs/index.md | 2 + docs/quickstart.md | 4 +- docs/release_notes.md | 1 + 7 files changed, 1297 insertions(+), 57 deletions(-) create mode 100644 docs/attrs.md diff --git a/androidplot-core/build.gradle b/androidplot-core/build.gradle index e063b01f..aff191ce 100644 --- a/androidplot-core/build.gradle +++ b/androidplot-core/build.gradle @@ -20,6 +20,42 @@ apply plugin: 'com.jfrog.bintray' apply plugin: 'com.vanniktech.android.junit.jacoco' apply plugin: 'com.github.kt3k.coveralls' +class AttrMarkdown extends DefaultTask { + + def inFile + def outFile + + @TaskAction + def generate() { + def input = project.file(inFile) + + def output = project.file(outFile) + if(output.exists()) { + output.delete() + } + output.parentFile.mkdirs() + + input.text.findAll(//) { match, g1 -> g1 + if(!g1.startsWith("NODOC")) { + output.append(g1) + output.append "\n\n" + } + } + } +} + +/** + * Generates xml attrs markdown docs. To run: + * at the command line from the project root dir type: + * ./gradlew generateAttrsMarkdown + * + * The generated doc will appear in / replace androidplot/docs/attrs.md + */ +task generateAttrsMarkdown(type: AttrMarkdown) { + inFile = { "src/main/res/values/attrs.xml"} + outFile = { "../docs/attrs.md" } +} + android { compileSdkVersion theCompileSdkVersion buildToolsVersion theBuildToolsVersion diff --git a/androidplot-core/src/main/java/com/androidplot/util/AttrUtils.java b/androidplot-core/src/main/java/com/androidplot/util/AttrUtils.java index b34e7d47..853ae25a 100644 --- a/androidplot-core/src/main/java/com/androidplot/util/AttrUtils.java +++ b/androidplot-core/src/main/java/com/androidplot/util/AttrUtils.java @@ -237,7 +237,7 @@ private static Number getIntFloatDimenValue(TypedArray attrs, int valueAttr, Num } else if (valueType == TypedValue.TYPE_FLOAT) { result = attrs.getFloat(valueAttr, defaultValue.floatValue()); } else { - throw new IllegalArgumentException("Invalid value type - must be float or dimension."); + throw new IllegalArgumentException("Invalid value type - must be int, float or dimension."); } } return result; diff --git a/androidplot-core/src/main/res/values/attrs.xml b/androidplot-core/src/main/res/values/attrs.xml index 07ad6022..e640706e 100644 --- a/androidplot-core/src/main/res/values/attrs.xml +++ b/androidplot-core/src/main/res/values/attrs.xml @@ -1,5 +1,5 @@ - + @@ -48,36 +55,35 @@ - + - - - + + + - + - + - + - + @@ -190,7 +196,7 @@ - + @@ -267,180 +273,819 @@ - + + + + + + + + + + + + + + + + + - + - + - + + - + + - - + + + + + + + + + + + + + - - + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + - + - + + + + - - + + + + + +Enable line labels on one or more edge of the graph. For example, to enable labels on the left +and bottom edges: +``` +ap:lineLabels="left|bottom" +``` +--> + +Text alignment of line labels drawn on top edge of the plot. This alignment is applied relative +to the line label insets defined by `lineLabelInsetTop`. +--> + + - + +Text alignment of line labels drawn on right edge of the plot. This alignment is applied relative +to the line label insets defined by `lineLabelInsetRight`. +--> + + + + + - + + + + + + + + - + + + + - - - - - + + + + + + + + + + + + - - + - - + - - + + + + + + + + + + + + + + + + - - + + - - + + + \ No newline at end of file diff --git a/docs/attrs.md b/docs/attrs.md new file mode 100644 index 00000000..00f79651 --- /dev/null +++ b/docs/attrs.md @@ -0,0 +1,556 @@ +# Androidplot XML Attributes +Attributes are broken down by element followed by either their type or list of accepted values. +
+
+_This documentation is auto generated from [attrs.xml](../androidplot-core/src/main/res/values/attrs.xml) and should not be edited directly._ + +## Plot +Plot's attrs are available in all Plot types. + +### markupEnabled +__boolean__ + +### renderMode +* use_background_thread +* use_main_thread + +### marginTop +__dimension__ + +### marginBottom +__dimension__ + +### marginLeft +__dimension__ + +### marginRight +__dimension__ + +### paddingTop +__dimension__ + +### paddingBottom +__dimension__ + +### paddingLeft +__dimension__ + +### paddingRight +__dimension__ + +### title +__dimension__ + +### titleTextSize +__string__ + +### titleTextColor +__dimension__ + +### backgroundColor +__color__ + +### borderColor +__color__ + +### borderThickness +__color__ + +## XYPlot +XML attributes for the [XYPlot](xyplot.md) class. + +### previewMode +TODO + +### domainStepMode +* subdivide +* increment_by_val +* increment_by_pixels + +### domainStep +__dimension|float|integer__ + +### rangeStepMode +* subdivide +* increment_by_val +* increment_by_pixels + +### rangeStep +__dimension|float|integer__ + +### domainTitle +__string__ + +### domainTitleTextColor +__color__ + +### domainTitleTextSize +__dimension__ + +### domainTitleHeightMode +* absolute +* relative +* fill + +### domainTitleWidthMode +* absolute +* relative +* fill + +### domainTitleHeight +__dimension|float|integer__ + +### domainTitleWidth +__dimension|float|integer__ + +### domainTitleHorizontalPositioning +* absolute_from_left +* absolute_from_right +* absolute_from_center +* relative_from_left +* relative_from_right +* relative_from_center + +### domainTitleVerticalPositioning +* absolute_from_top +* absolute_from_bottom +* absolute_from_center +* relative_from_top +* relative_from_bottom +* relative_from_center + +### domainTitleHorizontalPosition +__dimension|float|integer__ + +### domainTitleVerticalPosition +__dimension|float|integer__ + +### domainTitleAnchor +* top_middle +* left_top +* left_middle +* left_bottom +* right_top +* right_middle +* right_bottom +* bottom_middle +* center + +### domainTitleVisible +__boolean__ + +### rangeTitle +__string__ + +### rangeTitleColor +__color__ + +### rangeTitleTextSize +__dimension__ + +### rangeTitleHeightMode +* absolute +* relative +* fill + +### rangeTitleWidthMode +* absolute +* relative +* fill + +### rangeTitleHeight +__dimension|float|integer__ + +### rangeTitleWidth +__dimension|float|integer__ + +### rangeTitleHorizontalPositioning +* absolute_from_left +* absolute_from_right +* absolute_from_center +* relative_from_left +* relative_from_right +* relative_from_center + +### rangeTitleVerticalPositioning +* absolute_from_top +* absolute_from_bottom +* absolute_from_center +* relative_from_top +* relative_from_bottom +* relative_from_center + +### rangeTitleHorizontalPosition +__dimension|float|integer__ + +### rangeTitleVerticalPosition +__dimension|float|integer__ + +### rangeTitleAnchor +* top_middle +* left_top +* left_middle +* left_bottom +* right_top +* right_middle +* right_bottom +* bottom_middle +* center + +### rangeTitleVisible +__boolean__ + +### graphHeight +__boolean__ +
+(default is false) When set to true, grid lines are drawn on top of rendered series data +instead of underneath. + +### graphHeightMode +* absolute +* relative +* fill + +### graphWidthMode +* absolute +* relative +* fill + +### graphHeight +__dimension|float|integer__ + +### graphWidth +__dimension|float|integer__ + +### graphRotation +* none +* ninety_degrees +* negative_ninety_degrees +* one_hundred_eighty_degrees + +### graphHorizontalPositioning +* absolute_from_left +* absolute_from_right +* absolute_from_center +* relative_from_left +* relative_from_right +* relative_from_center + +### graphVerticalPositioning +* absolute_from_top +* absolute_from_bottom +* absolute_from_center +* relative_from_top +* relative_from_bottom +* relative_from_center + +### graphHorizontalPosition +__dimension|float|integer__ + +### graphVerticalPosition +__dimension|float|integer__ + +### graphAnchor +* top_middle +* left_top +* left_middle +* left_bottom +* right_top +* right_middle +* right_bottom +* bottom_middle +* center + +### graphVisible +__boolean__ + +### graphMarginTop +__dimension__ + +### graphMarginBottom +__dimension__ + +### graphMarginLeft +__dimension__ + +### graphMarginRight +__dimension__ + +### graphPaddingTop +__dimension__ + +### graphPaddingBottom +__dimension__ + +### graphPaddingLeft +__dimension__ + +### graphPaddinRight +__dimension__ + +### gridClippingEnabled +__boolean__ + +### gridInsetTop +__dimension__ + +### gridInsetBottom +__dimension__ + +### gridInsetLeft +__dimension__ + +### gridInsetRight +__dimension__ + +### lineLabelInsetTop +__dimension__ +
+Top edge of a rectangle relative to the XYGraphWidget +border upon which line labels will be anchored. + +### lineLabelInsetBottom +__dimension__ +
+Bottom edge of a rectangle relative to the XYGraphWidget +border upon which line labels will be anchored. + +### lineLabelInsetLeft +__dimension__ +
+Left edge of a rectangle relative to the XYGraphWidget +border upon which line labels will be anchored. + +### lineLabelInsetRight +__dimension__ +
+Right edge of a rectangle relative to the XYGraphWidget +border upon which line labels will be anchored. + +### lineLabels +* top +* bottom +* left +* right + +Enable line labels on one or more edge of the graph. For example, to enable labels on the left +and bottom edges: +``` +ap:lineLabels="left|bottom" +``` + +### lineLabelAlignTop +* left +* center +* right + +Text alignment of line labels drawn on top edge of the plot. This alignment is applied relative +to the line label insets defined by `lineLabelInsetTop`. + +### lineLabelAlignBottom +* left +* center +* right + +Text alignment of line labels drawn on bottom edge of the plot. This alignment is applied relative +to the line label insets defined by `lineLabelInsetBottom`. + +### lineLabelAlignLeft +* left +* center +* right + +Text alignment of line labels drawn on left edge of the plot. This alignment is applied relative +to the line label insets defined by `lineLabelInsetLeft`. + +### lineLabelAlignRight +* left +* center +* right + +Text alignment of line labels drawn on right edge of the plot. This alignment is applied relative +to the line label insets defined by `lineLabelInsetRight`. + +### lineLabelRotationTop +__float__ +
+Angle at which line labels on the plot's top edge are drawn. + +### lineLabelRotationBottom +__float__ +
+Angle at which line labels on the plot's bottom edge are drawn. + +### lineLabelRotationLet +__float__ +
+Angle at which line labels on the plot's left edge are drawn. + +### lineLabelRotationRight +__float__ +
+Angle at which line labels on the plot's right edge are drawn. + +### domainLineThickness +__dimension__ + +### rangeLineThickness +__dimension__ + +### domainLineColor +__color__ + +### rangeLineColor +__color__ + +### domainOriginLineThickness +__dimension__ + +### rangeOriginLineThickness +__dimension__ + +### domainOriginLineColor +__color__ + +### rangeOriginLineColor +__color__ + +### lineLabelTextSizeTop +__dimension__ + +### lineLabelTextSizeBottom +__dimension__ + +### lineLabelTextSizeLeft +__dimension__ + +### lineLabelTextSizeRight +__dimension__ + +### lineLabelTextColorTop +__color__ + +### lineLabelTextColorBottom +__color__ + +### lineLabelTextColorLeft +__color__ + +### lineLabelTextColorRight +__color__ + +### lineExtensionTop +__dimension__ + +### lineExtensionBottom +__dimension__ + +### lineExtensionLeft +__dimension__ + +### lineExtensionRight +__dimension__ + +### gridBackgroundColor +__color__ +
+background color of the grid portion of the XYGraphWidget + +### graphBackgroundColor +__color__ +
+background color of the XYGraphWidget + +### legendHeightMode +* absolute +* relative +* fill + +### legendWidthMode +* absolute +* relative +* fill + +### legendHeight +__dimension|float|integer__ + +### legendWidth +__dimension|float|integer__ + +### legendHorizontalPositioning +* absolute_from_left +* absolute_from_right +* absolute_from_center +* relative_from_left +* relative_from_right +* relative_from_center + +### legendVerticalPositioning +* absolute_from_top +* absolute_from_bottom +* absolute_from_center +* relative_from_top +* relative_from_bottom +* relative_from_center + +### legendHorizontalPosition +__dimension|float|integer__ + +### legendVerticalPosition +__dimension|float|integer__ + +### legendAnchor +* top_middle +* left_top +* left_middle +* left_bottom +* right_top +* right_middle +* right_bottom +* bottom_middle +* center + +### legendTextSize +__dimension__ + +### legendTextColor +__color__ + +### legendIconHeightMode +* absolute +* relative +* fill + +### legendIconWidthMode +* absolute +* relative +* fill + +### legendIconHeight +__dimension|float|integer__ + +### legendIconWidth +__dimension|float|integer__ + +### legendVisible +__boolean__ + +### pieBorderThickness +__dimension__ +
+Determines how far beyond the graph's edge each domain grid line will extend. + +### pieBorderThickness +__dimension__ +
+Determines how far beyond the graph's edge each range grid line will extend. + +## PieChart +TODO + +### pieBorderColor +__color__ + +### pieBorderThickness +__dimension__ + diff --git a/docs/index.md b/docs/index.md index 16d136a5..c42b6b06 100644 --- a/docs/index.md +++ b/docs/index.md @@ -37,6 +37,8 @@ Source code examples of the various plot types. * [An ECG Example](../demoapp/src/main/java/com/androidplot/demos/ECGExample.java) * [f(x) Plot](../demoapp/src/main/java/com/androidplot/demos/FXPlotExampleActivity.java) +# XML Attributes +A complete list of XML attributes is [available here](attrs.md). # Javadoc The latest Javadocs are [available here](https://circleci.com/api/v1/project/halfhp/androidplot/latest/artifacts/0/$CIRCLE_ARTIFACTS/javadoc/index.html). diff --git a/docs/quickstart.md b/docs/quickstart.md index 57bd090c..41d28a64 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -44,8 +44,8 @@ and add an XYPlot view: ap:lineLabelRotationBottom="-45"/> ``` -This example uses a default style to decorate the plot. The full list of styleable attributes is -[available here](../androidplot-core/src/main/res/values/attrs.xml). While new attributes are added regularly, +This example uses a default style to decorate the plot. The full list of XML styleable attributes is +[available here](attrs.md). While new attributes are added regularly, not all configurable properties are yet available. If something you need is missing, use [Fig Syntax](https://github.com/halfhp/fig) diff --git a/docs/release_notes.md b/docs/release_notes.md index 24491271..5b3cea1f 100644 --- a/docs/release_notes.md +++ b/docs/release_notes.md @@ -4,6 +4,7 @@ For details on what to expect in general when updating to a new version of Andro # 1.4.3 * (#39) `FastLineAndPointRenderer` now renders vertices for legend items. +* Added [XML Attrs reference doc](attrs.md). (Incomplete) # 1.4.2 * (#32) New step mode: `INCREMENT_BY_FIT`. From a3c4ca3bb01e0095a7358473b3f02ed731debe3b Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Fri, 12 May 2017 07:31:09 -0400 Subject: [PATCH 20/81] uprev to 1.4.4 --- build.gradle | 2 +- docs/quickstart.md | 2 +- docs/release_notes.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 973ff339..dd84178e 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ ext { theCompileSdkVersion = 25 theTargetSdkVersion = 25 theMinSdkVersion = 5 - theVersionName = '1.4.3' + theVersionName = '1.4.4' theVersionCode = 0 } diff --git a/docs/quickstart.md b/docs/quickstart.md index 41d28a64..4c629126 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -14,7 +14,7 @@ To use the library in your gradle project add the following to your build.gradle ```groovy dependencies { - compile "com.androidplot:androidplot-core:1.4.2" + compile "com.androidplot:androidplot-core:1.4.3" } ``` diff --git a/docs/release_notes.md b/docs/release_notes.md index 5b3cea1f..d9a0ef4a 100644 --- a/docs/release_notes.md +++ b/docs/release_notes.md @@ -4,7 +4,7 @@ For details on what to expect in general when updating to a new version of Andro # 1.4.3 * (#39) `FastLineAndPointRenderer` now renders vertices for legend items. -* Added [XML Attrs reference doc](attrs.md). (Incomplete) +* Added [XML Attrs reference doc](attrs.md). # 1.4.2 * (#32) New step mode: `INCREMENT_BY_FIT`. From 633ac9854a00435f25498818c577de265851ec6e Mon Sep 17 00:00:00 2001 From: Jonathan Date: Fri, 19 May 2017 19:36:52 -0500 Subject: [PATCH 21/81] Corrected documentation for `OrderedXYSeries` (#42) --- .../src/main/java/com/androidplot/xy/OrderedXYSeries.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/androidplot-core/src/main/java/com/androidplot/xy/OrderedXYSeries.java b/androidplot-core/src/main/java/com/androidplot/xy/OrderedXYSeries.java index 659e38ea..820e4330 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/OrderedXYSeries.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/OrderedXYSeries.java @@ -9,13 +9,13 @@ public interface OrderedXYSeries extends XYSeries { enum XOrder { /** * XVals are in strict ascending order such that: - * x(i) > x(i+1) == true + * x(i) < x(i+1) == true */ ASCENDING, /** * XVals are in strict descending order such that: - * x(i) < x(i+1) == true + * x(i) > x(i+1) == true */ DESCENDING, From ef94704bf105841c316c81f7507e4e5e5a2ee789 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Mon, 22 May 2017 22:23:23 -0500 Subject: [PATCH 22/81] Implement descriptive toString() methods for RectRegion and related classes (#44) adds more descriptive toString implementations to various classes --- .../src/main/java/com/androidplot/Region.java | 18 ++++++- .../java/com/androidplot/util/FastNumber.java | 5 ++ .../com/androidplot/util/SeriesUtils.java | 24 +++++---- .../java/com/androidplot/xy/RectRegion.java | 10 ++++ .../com/androidplot/xy/XYConstraints.java | 52 +++++++++++++------ 5 files changed, 81 insertions(+), 28 deletions(-) diff --git a/androidplot-core/src/main/java/com/androidplot/Region.java b/androidplot-core/src/main/java/com/androidplot/Region.java index 2ace04a7..c8820f59 100644 --- a/androidplot-core/src/main/java/com/androidplot/Region.java +++ b/androidplot-core/src/main/java/com/androidplot/Region.java @@ -17,7 +17,7 @@ package com.androidplot; -import com.androidplot.util.*; +import com.androidplot.util.FastNumber; /** * A one dimensional region represented by a starting and ending value. @@ -249,4 +249,20 @@ public void setMax(Number max) { public boolean isDefined() { return min != null && max != null; } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("Region{"); + sb.append("min=").append(min); + sb.append(", max=").append(max); + sb.append(", cachedLength=").append(cachedLength); + sb.append(", defaults="); + if (defaults != this) { + sb.append(defaults); + } else { + sb.append("this"); + } + sb.append('}'); + return sb.toString(); + } } diff --git a/androidplot-core/src/main/java/com/androidplot/util/FastNumber.java b/androidplot-core/src/main/java/com/androidplot/util/FastNumber.java index 049431b3..d54daca4 100644 --- a/androidplot-core/src/main/java/com/androidplot/util/FastNumber.java +++ b/androidplot-core/src/main/java/com/androidplot/util/FastNumber.java @@ -63,4 +63,9 @@ public double doubleValue() { } return doublePrimitive; } + + @Override + public String toString() { + return String.valueOf(doubleValue()); + } } diff --git a/androidplot-core/src/main/java/com/androidplot/util/SeriesUtils.java b/androidplot-core/src/main/java/com/androidplot/util/SeriesUtils.java index a9f09192..e74a8f3b 100644 --- a/androidplot-core/src/main/java/com/androidplot/util/SeriesUtils.java +++ b/androidplot-core/src/main/java/com/androidplot/util/SeriesUtils.java @@ -16,10 +16,14 @@ package com.androidplot.util; -import com.androidplot.*; -import com.androidplot.xy.*; +import com.androidplot.Region; +import com.androidplot.xy.FastXYSeries; +import com.androidplot.xy.OrderedXYSeries; +import com.androidplot.xy.RectRegion; +import com.androidplot.xy.XYConstraints; +import com.androidplot.xy.XYSeries; -import java.util.*; +import java.util.List; /** * Utilities for dealing with Series data. @@ -83,18 +87,18 @@ public static RectRegion minMax(XYConstraints constraints, XYSeries... seriesArr // if this is an advanced xy series then minMax have already been calculated for us: if(series instanceof FastXYSeries) { - final RectRegion b = ((FastXYSeries) series).minMax(); - if(b == null) { + final RectRegion seriesBounds = ((FastXYSeries) series).minMax(); + if (seriesBounds == null) { continue; } if(constraints == null) { - bounds.union(b); + bounds.union(seriesBounds); } else { - if(constraints.contains(b.getMinX(), b.getMinY())) { - bounds.union(b.getMinX(), b.getMinY()); + if (constraints.contains(seriesBounds.getMinX(), seriesBounds.getMinY())) { + bounds.union(seriesBounds.getMinX(), seriesBounds.getMinY()); } - if(constraints.contains(b.getMaxX(), b.getMaxY())) { - bounds.union(b.getMaxX(), b.getMaxY()); + if (constraints.contains(seriesBounds.getMaxX(), seriesBounds.getMaxY())) { + bounds.union(seriesBounds.getMaxX(), seriesBounds.getMaxY()); } } diff --git a/androidplot-core/src/main/java/com/androidplot/xy/RectRegion.java b/androidplot-core/src/main/java/com/androidplot/xy/RectRegion.java index 7eeb0d73..5a1433f7 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/RectRegion.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/RectRegion.java @@ -20,6 +20,7 @@ import android.graphics.RectF; import com.androidplot.Region; + import java.util.ArrayList; import java.util.List; @@ -344,4 +345,13 @@ public boolean isFullyDefined() { public boolean contains(Number x, Number y) { return getxRegion().contains(x) && getyRegion().contains(y); } + + @Override + public String toString() { + return "RectRegion{" + + "xRegion=" + xRegion + + ", yRegion=" + yRegion + + ", label='" + label + '\'' + + '}'; + } } diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYConstraints.java b/androidplot-core/src/main/java/com/androidplot/xy/XYConstraints.java index ae22a4ea..2ee06adf 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYConstraints.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYConstraints.java @@ -18,6 +18,7 @@ /** * Calculates the min/max constraints for an xy plane. + * * @since 0.9.7 */ public class XYConstraints { @@ -52,26 +53,26 @@ public XYConstraints(Number minX, Number maxX, Number minY, Number maxY) { } public boolean contains(Number x, Number y) { - if(x == null || y == null) { + if (x == null || y == null) { // this is essentially an invisible point: return false; - } else { - final double dx = x.doubleValue(); - - if(minX != null && dx < minX.doubleValue()) { - return false; - } else if(maxX != null && dx > maxX.doubleValue()) { - return false; - } else { - final double dy = y.doubleValue(); - if(minY != null && dy < minY.doubleValue()) { - return false; - } else if(maxY != null && dy > maxY.doubleValue()) { - return false; - } - } - return true; } + + final double dx = x.doubleValue(); + if (minX != null && dx < minX.doubleValue()) { + return false; + } else if (maxX != null && dx > maxX.doubleValue()) { + return false; + } + + final double dy = y.doubleValue(); + if (minY != null && dy < minY.doubleValue()) { + return false; + } else if (maxY != null && dy > maxY.doubleValue()) { + return false; + } + + return true; } public Number getMinX() { @@ -153,4 +154,21 @@ public void setMinY(Number minY) { public void setMaxY(Number maxY) { this.maxY = maxY; } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("XYConstraints{"); + sb.append("domainFramingModel=").append(domainFramingModel); + sb.append(", rangeFramingModel=").append(rangeFramingModel); + sb.append(", domainUpperBoundaryMode=").append(domainUpperBoundaryMode); + sb.append(", domainLowerBoundaryMode=").append(domainLowerBoundaryMode); + sb.append(", rangeUpperBoundaryMode=").append(rangeUpperBoundaryMode); + sb.append(", rangeLowerBoundaryMode=").append(rangeLowerBoundaryMode); + sb.append(", minX=").append(minX); + sb.append(", maxX=").append(maxX); + sb.append(", minY=").append(minY); + sb.append(", maxY=").append(maxY); + sb.append('}'); + return sb.toString(); + } } From 7dc7055e5c7eee2d99c0343a79ba072e9d1d34af Mon Sep 17 00:00:00 2001 From: Jonathan Date: Fri, 26 May 2017 17:41:20 -0500 Subject: [PATCH 23/81] Many Performance Improvements (#47) * Corrected documentation for `OrderedXYSeries` * Rename local variable in SeriesUtils.java to better describe its contents. Android studio also replaced wildcards in imports probably to match AOSP style guides https://source.android.com/source/code-style#fully-qualify-imports * Implements `toString` methods for `RectRegion` and related classes for easier debugging. * Refactoring only - reduced nesting in XYConstraints.java * Rename local variable in SeriesUtils.java to better describe its contents. Android studio also replaced wildcards in imports probably to match AOSP style guides https://source.android.com/source/code-style#fully-qualify-imports * Implements `toString` methods for `RectRegion` and related classes for easier debugging. * Refactoring only - reduced nesting in XYConstraints.java * Added shortcut to XYConstraints.java#contains for situations where there are no defined constraints. This avoids getting the double value of two numbers. * Removed redundant array creation in Redrawer.java * Replaced manual array copy operations with possibly faster method (will vary by android device) * Fixed `FastLineAndPointRenderer` allocating many extra instances of `PointF` which hurts android performance. * Prevent creation of a new instance of `FastNumber` when setting the min or max of a `Region` * Replaced empty string concatenation with more efficient `String.valueOf` * Replaced HashMaps with EnumMaps where possible in XYGraphWidget.java * Replaced stringbuffer with string in XYConstraints.java#toString() * Replaced HashSet with EnumSet * Replace float new instance with Float.valueOf * Replaced StringBuffer with StringBuilder * set initial array sizes * Removed the primative cache from FastNumber#equals and FastNumber#hashcode() and made the number field final * Made methods static where possible * Add Unit Tests for FastNumber.java#equals and FastNumber.java#hashCode * FastNumber.java does not allow null * Annotated all method/parameters in overrides which aren't annotated as the method/parameter they override. * Inferred nullity annotations for `FastNumber` and `FixedSizeEditableXYSeries` * fix typo * Simplified unit tests for FastNumberTest.java --- .../src/main/java/com/androidplot/Region.java | 6 +- .../java/com/androidplot/SeriesRegistry.java | 4 +- .../com/androidplot/ui/DynamicTableModel.java | 4 +- .../com/androidplot/ui/LayoutManager.java | 10 +- .../com/androidplot/ui/PositionMetrics.java | 4 +- .../java/com/androidplot/util/FastNumber.java | 39 ++++- .../java/com/androidplot/util/Redrawer.java | 9 +- .../com/androidplot/xy/BubbleFormatter.java | 11 +- .../xy/FastLineAndPointRenderer.java | 16 ++- .../xy/FixedSizeEditableXYSeries.java | 15 +- .../java/com/androidplot/xy/RectRegion.java | 2 +- .../com/androidplot/xy/SimpleXYSeries.java | 13 +- .../com/androidplot/xy/XYConstraints.java | 29 ++-- .../com/androidplot/xy/XYGraphWidget.java | 54 ++++--- .../main/java/com/androidplot/xy/XYPlot.java | 14 +- .../com/androidplot/xy/XYSeriesFormatter.java | 4 +- .../com/androidplot/util/FastNumberTest.java | 135 ++++++++++++++++++ .../demos/AnimatedXYPlotActivity.java | 7 +- .../demos/CandlestickChartActivity.java | 29 ++-- .../androidplot/demos/DualScaleActivity.java | 20 +-- .../androidplot/demos/ListViewActivity.java | 11 +- .../demos/SimpleXYPlotActivity.java | 17 ++- .../demos/StepChartExampleActivity.java | 18 ++- .../androidplot/demos/TimeSeriesActivity.java | 26 +++- 24 files changed, 380 insertions(+), 117 deletions(-) create mode 100644 androidplot-core/src/test/java/com/androidplot/util/FastNumberTest.java diff --git a/androidplot-core/src/main/java/com/androidplot/Region.java b/androidplot-core/src/main/java/com/androidplot/Region.java index c8820f59..3f67bb09 100644 --- a/androidplot-core/src/main/java/com/androidplot/Region.java +++ b/androidplot-core/src/main/java/com/androidplot/Region.java @@ -215,7 +215,7 @@ public void setMin(Number min) { } else { this.min = null; } - } else { + } else if (this.min == null || !this.min.equals(min)) { this.min = new FastNumber(min); } } @@ -237,7 +237,7 @@ public void setMax(Number max) { } else { this.max = null; } - } else { + } else if (this.max == null || !this.max.equals(max)) { this.max = new FastNumber(max); } } @@ -252,7 +252,7 @@ public boolean isDefined() { @Override public String toString() { - final StringBuffer sb = new StringBuffer("Region{"); + final StringBuilder sb = new StringBuilder("Region{"); sb.append("min=").append(min); sb.append(", max=").append(max); sb.append(", cachedLength=").append(cachedLength); diff --git a/androidplot-core/src/main/java/com/androidplot/SeriesRegistry.java b/androidplot-core/src/main/java/com/androidplot/SeriesRegistry.java index 78f3d805..7a7b3023 100644 --- a/androidplot-core/src/main/java/com/androidplot/SeriesRegistry.java +++ b/androidplot-core/src/main/java/com/androidplot/SeriesRegistry.java @@ -19,7 +19,7 @@ import com.androidplot.ui.Formatter; import com.androidplot.ui.SeriesBundle; -import java.io.*; +import java.io.Serializable; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -38,7 +38,7 @@ public List getSeriesAndFormatterList() { return registry; } public List getSeriesList() { - List result = new ArrayList<>(); + List result = new ArrayList<>(registry.size()); for(SeriesBundle sfPair : registry) { result.add(sfPair.getSeries()); } diff --git a/androidplot-core/src/main/java/com/androidplot/ui/DynamicTableModel.java b/androidplot-core/src/main/java/com/androidplot/ui/DynamicTableModel.java index c7c7f066..ff973b9d 100644 --- a/androidplot-core/src/main/java/com/androidplot/ui/DynamicTableModel.java +++ b/androidplot-core/src/main/java/com/androidplot/ui/DynamicTableModel.java @@ -149,10 +149,10 @@ public TableModelIterator(DynamicTableModel dynamicTableModel, RectF tableRect, calculatedRows = dynamicTableModel.getNumRows(); // round up: - calculatedColumns = new Float((totalElements / (float) calculatedRows) + 0.5).intValue(); + calculatedColumns = Float.valueOf((totalElements / (float) calculatedRows) + 0.5f).intValue(); } else if(dynamicTableModel.getNumRows() == 0 && dynamicTableModel.getNumColumns() >= 1) { calculatedColumns = dynamicTableModel.getNumColumns(); - calculatedRows = new Float((totalElements / (float) calculatedColumns) + 0.5).intValue(); + calculatedRows = Float.valueOf((totalElements / (float) calculatedColumns) + 0.5f).intValue(); // unlimited rows and columns (impossible) so default a single row with n columns: }else if(dynamicTableModel.getNumColumns() == 0 && dynamicTableModel.getNumRows() == 0) { calculatedRows = 1; diff --git a/androidplot-core/src/main/java/com/androidplot/ui/LayoutManager.java b/androidplot-core/src/main/java/com/androidplot/ui/LayoutManager.java index d9107442..9e66e80c 100644 --- a/androidplot-core/src/main/java/com/androidplot/ui/LayoutManager.java +++ b/androidplot-core/src/main/java/com/androidplot/ui/LayoutManager.java @@ -16,9 +16,15 @@ package com.androidplot.ui; -import android.graphics.*; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PointF; +import android.graphics.RectF; +import android.graphics.Region; import android.view.MotionEvent; import android.view.View; + import com.androidplot.exception.PlotRenderException; import com.androidplot.ui.widget.Widget; import com.androidplot.util.DisplayDimensions; @@ -132,7 +138,7 @@ public void draw(Canvas canvas) throws PlotRenderException { } } - private void drawSpacing(Canvas canvas, RectF outer, RectF inner, Paint paint) { + private static void drawSpacing(Canvas canvas, RectF outer, RectF inner, Paint paint) { try { canvas.save(Canvas.ALL_SAVE_FLAG); canvas.clipRect(inner, Region.Op.DIFFERENCE); diff --git a/androidplot-core/src/main/java/com/androidplot/ui/PositionMetrics.java b/androidplot-core/src/main/java/com/androidplot/ui/PositionMetrics.java index 3381f20f..88efddcb 100644 --- a/androidplot-core/src/main/java/com/androidplot/ui/PositionMetrics.java +++ b/androidplot-core/src/main/java/com/androidplot/ui/PositionMetrics.java @@ -16,6 +16,8 @@ package com.androidplot.ui; +import android.support.annotation.NonNull; + public class PositionMetrics implements Comparable { private HorizontalPosition horizontalPosition; @@ -47,7 +49,7 @@ public void setAnchor(Anchor anchor) { } @Override - public int compareTo(PositionMetrics o) { + public int compareTo(@NonNull PositionMetrics o) { if(this.layerDepth < o.layerDepth) { return -1; } else if(this.layerDepth == o.layerDepth) { diff --git a/androidplot-core/src/main/java/com/androidplot/util/FastNumber.java b/androidplot-core/src/main/java/com/androidplot/util/FastNumber.java index d54daca4..4b113b5d 100644 --- a/androidplot-core/src/main/java/com/androidplot/util/FastNumber.java +++ b/androidplot-core/src/main/java/com/androidplot/util/FastNumber.java @@ -1,11 +1,15 @@ package com.androidplot.util; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + /** * An extension of {@link Number} optimized for speed at the cost of memory. */ public class FastNumber extends Number { - private Number number; + @NonNull + private final Number number; private boolean hasDoublePrimitive; private boolean hasFloatPrimitive; private boolean hasIntPrimitive; @@ -14,7 +18,11 @@ public class FastNumber extends Number { private float floatPrimitive; private int intPrimitive; - public FastNumber(Number number) { + public FastNumber(@NonNull Number number) { + //noinspection ConstantConditions //in case someone ignores the @NonNull annotation + if (number == null) { + throw new IllegalArgumentException("number parameter cannot be null"); + } // avoid nested instances of FastNumber : if(number instanceof FastNumber) { @@ -64,6 +72,33 @@ public double doubleValue() { return doublePrimitive; } + /** + * To be equal, two instances must both be instances of {@link FastNumber}. The inner {@link + * #number} field must also be a common type. Numbers which are mathematically equal are not + * necessarily equal. This keeps with the java implementation of common Number classes where for + * instance {@code new Integer(0).equals(new Double(0))} returns {@code false} + */ + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + FastNumber that = (FastNumber) o; + + return number.equals(that.number); + + } + + @Override + public int hashCode() { + return number.hashCode(); + } + + @NonNull @Override public String toString() { return String.valueOf(doubleValue()); diff --git a/androidplot-core/src/main/java/com/androidplot/util/Redrawer.java b/androidplot-core/src/main/java/com/androidplot/util/Redrawer.java index 00174027..6cf3b4eb 100644 --- a/androidplot-core/src/main/java/com/androidplot/util/Redrawer.java +++ b/androidplot-core/src/main/java/com/androidplot/util/Redrawer.java @@ -17,11 +17,12 @@ package com.androidplot.util; import android.util.Log; + import com.androidplot.Plot; import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.List; /** @@ -52,7 +53,7 @@ public class Redrawer implements Runnable { * @param startImmediately If true, invokes run() immediately after construction. */ public Redrawer(List plots, float maxRefreshRate, boolean startImmediately) { - this.plots = new ArrayList<>(); + this.plots = new ArrayList<>(plots.size()); for(Plot plot : plots) { this.plots.add(new WeakReference<>(plot)); } @@ -65,7 +66,7 @@ public Redrawer(List plots, float maxRefreshRate, boolean startImmediately } public Redrawer(Plot plot, float maxRefreshRate, boolean startImmediately) { - this(Arrays.asList(new Plot[]{plot}), maxRefreshRate, startImmediately); + this(Collections.singletonList(plot), maxRefreshRate, startImmediately); } /** @@ -121,7 +122,7 @@ public void run() { } } } - } catch(InterruptedException e) { + } catch (InterruptedException ignored) { } finally { Log.d(TAG, "Redrawer thread exited."); diff --git a/androidplot-core/src/main/java/com/androidplot/xy/BubbleFormatter.java b/androidplot-core/src/main/java/com/androidplot/xy/BubbleFormatter.java index b351358b..3a794b5c 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/BubbleFormatter.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/BubbleFormatter.java @@ -16,11 +16,12 @@ package com.androidplot.xy; -import android.content.*; -import android.graphics.*; +import android.content.Context; +import android.graphics.Color; +import android.graphics.Paint; -import com.androidplot.ui.*; -import com.androidplot.util.*; +import com.androidplot.ui.SeriesRenderer; +import com.androidplot.util.PixelUtils; /** * Format for drawing a value using {@link BubbleRenderer}. @@ -51,7 +52,7 @@ public class BubbleFormatter extends XYSeriesFormatter { setPointLabeler(new PointLabeler() { @Override public String getLabel(BubbleSeries series, int index) { - return series.getZ(index) + ""; + return String.valueOf(series.getZ(index)); } }); } diff --git a/androidplot-core/src/main/java/com/androidplot/xy/FastLineAndPointRenderer.java b/androidplot-core/src/main/java/com/androidplot/xy/FastLineAndPointRenderer.java index 7aaa58a8..4e117252 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/FastLineAndPointRenderer.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/FastLineAndPointRenderer.java @@ -16,12 +16,16 @@ package com.androidplot.xy; -import android.graphics.*; +import android.graphics.Canvas; +import android.graphics.PointF; +import android.graphics.RectF; + import com.androidplot.exception.PlotRenderException; import com.androidplot.ui.RenderStack; import com.androidplot.ui.SeriesRenderer; -import java.util.*; +import java.util.ArrayList; +import java.util.List; /** * A faster implementation of of {@link LineAndPointRenderer}. For performance reasons, has these constraints: @@ -53,11 +57,11 @@ protected void onRender(Canvas canvas, RectF plotArea, XYSeries series, Formatte int segmentLen = 0; boolean isLastPointNull = true; + PointF resultPoint = new PointF(); for (int i = 0, j = 0; i < series.size(); i++, j+=2) { Number y = series.getY(i); Number x = series.getX(i); - PointF thisPoint; if (y != null && x != null) { if(isLastPointNull) { segmentOffsets.add(j); @@ -65,9 +69,9 @@ protected void onRender(Canvas canvas, RectF plotArea, XYSeries series, Formatte isLastPointNull = false; } - thisPoint = getPlot().getBounds().transformScreen(x, y, plotArea); - points[j] = thisPoint.x; - points[j+1] = thisPoint.y; + getPlot().getBounds().transformScreen(resultPoint, x, y, plotArea); + points[j] = resultPoint.x; + points[j + 1] = resultPoint.y; segmentLen+=2; // if this is the last point, account for it in segment lengths: diff --git a/androidplot-core/src/main/java/com/androidplot/xy/FixedSizeEditableXYSeries.java b/androidplot-core/src/main/java/com/androidplot/xy/FixedSizeEditableXYSeries.java index 240f58dc..03e328c9 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/FixedSizeEditableXYSeries.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/FixedSizeEditableXYSeries.java @@ -1,8 +1,11 @@ package com.androidplot.xy; -import com.androidplot.util.*; +import android.support.annotation.NonNull; -import java.util.*; +import com.androidplot.util.FastNumber; + +import java.util.ArrayList; +import java.util.List; /** * An efficient implementation of {@link EditableXYSeries} intended for use cases where @@ -14,7 +17,9 @@ */ public class FixedSizeEditableXYSeries implements EditableXYSeries { + @NonNull private List xVals = new ArrayList<>(); + @NonNull private List yVals = new ArrayList<>(); private String title; @@ -24,12 +29,12 @@ public FixedSizeEditableXYSeries(String title, int size) { } @Override - public void setX(Number x, int index) { + public void setX(@NonNull Number x, int index) { xVals.set(index, new FastNumber(x)); } @Override - public void setY(Number y, int index) { + public void setY(@NonNull Number y, int index) { yVals.set(index, new FastNumber(y)); } @@ -44,7 +49,7 @@ public void resize(int size) { resize(yVals, size); } - protected void resize(List list, int size) { + protected void resize(@NonNull List list, int size) { if (size > list.size()) { while (list.size() < size) { list.add(null); diff --git a/androidplot-core/src/main/java/com/androidplot/xy/RectRegion.java b/androidplot-core/src/main/java/com/androidplot/xy/RectRegion.java index 5a1433f7..0bb8988d 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/RectRegion.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/RectRegion.java @@ -245,7 +245,7 @@ public Number getHeight() { * @param y * @return */ - private Number distanceBetween(Number x, Number y) { + private static Number distanceBetween(Number x, Number y) { return Math.abs(x.doubleValue() - y.doubleValue()); } diff --git a/androidplot-core/src/main/java/com/androidplot/xy/SimpleXYSeries.java b/androidplot-core/src/main/java/com/androidplot/xy/SimpleXYSeries.java index f346694a..4d3ebaac 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/SimpleXYSeries.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/SimpleXYSeries.java @@ -17,10 +17,15 @@ package com.androidplot.xy; import android.graphics.Canvas; + import com.androidplot.Plot; import com.androidplot.PlotListener; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.NoSuchElementException; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -82,10 +87,8 @@ public void onAfterDraw(Plot source, Canvas canvas) { } protected static List asNumberList(Number... model) { - List numbers = new ArrayList<>(); - for(Number n : model) { - numbers.add(n); - } + List numbers = new ArrayList<>(model.length); + Collections.addAll(numbers, model); return numbers; } diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYConstraints.java b/androidplot-core/src/main/java/com/androidplot/xy/XYConstraints.java index 2ee06adf..705ae635 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYConstraints.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYConstraints.java @@ -58,6 +58,11 @@ public boolean contains(Number x, Number y) { return false; } + if (minX == null && maxX == null && minY == null && maxY == null) { + //there are no constraints + return true; + } + final double dx = x.doubleValue(); if (minX != null && dx < minX.doubleValue()) { return false; @@ -157,18 +162,16 @@ public void setMaxY(Number maxY) { @Override public String toString() { - final StringBuffer sb = new StringBuffer("XYConstraints{"); - sb.append("domainFramingModel=").append(domainFramingModel); - sb.append(", rangeFramingModel=").append(rangeFramingModel); - sb.append(", domainUpperBoundaryMode=").append(domainUpperBoundaryMode); - sb.append(", domainLowerBoundaryMode=").append(domainLowerBoundaryMode); - sb.append(", rangeUpperBoundaryMode=").append(rangeUpperBoundaryMode); - sb.append(", rangeLowerBoundaryMode=").append(rangeLowerBoundaryMode); - sb.append(", minX=").append(minX); - sb.append(", maxX=").append(maxX); - sb.append(", minY=").append(minY); - sb.append(", maxY=").append(maxY); - sb.append('}'); - return sb.toString(); + return "XYConstraints{" + "domainFramingModel=" + domainFramingModel + + ", rangeFramingModel=" + rangeFramingModel + + ", domainUpperBoundaryMode=" + domainUpperBoundaryMode + + ", domainLowerBoundaryMode=" + domainLowerBoundaryMode + + ", rangeUpperBoundaryMode=" + rangeUpperBoundaryMode + + ", rangeLowerBoundaryMode=" + rangeLowerBoundaryMode + + ", minX=" + minX + + ", maxX=" + maxX + + ", minY=" + minY + + ", maxY=" + maxY + + '}'; } } 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 21c512f6..a6460799 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java @@ -16,19 +16,33 @@ package com.androidplot.xy; -import android.content.res.*; -import android.graphics.*; - -import com.androidplot.*; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PointF; +import android.graphics.RectF; + +import com.androidplot.R; import com.androidplot.Region; import com.androidplot.exception.PlotRenderException; -import com.androidplot.ui.*; +import com.androidplot.ui.Insets; +import com.androidplot.ui.LayoutManager; +import com.androidplot.ui.RenderStack; +import com.androidplot.ui.Size; import com.androidplot.ui.widget.Widget; -import com.androidplot.util.*; +import com.androidplot.util.AttrUtils; +import com.androidplot.util.FontUtils; +import com.androidplot.util.PixelUtils; +import com.androidplot.util.RectFUtils; import java.text.DecimalFormat; import java.text.Format; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.Map; /** * Displays graphical data (lines, points, etc.) annotated with domain and range tick markers. @@ -121,14 +135,14 @@ public class XYGraphWidget extends Widget { /** * Set of edges for which line labels should be displayed */ - private Set lineLabelEdges = new HashSet<>(); + private EnumSet lineLabelEdges = EnumSet.noneOf(Edge.class); private RenderStack renderStack; private CursorLabelFormatter cursorLabelFormatter; - private HashMap lineLabelStyles = getDefaultLineLabelStyles(); - private HashMap lineLabelRenderers = getDefaultLineLabelRenderers(); + private Map lineLabelStyles = getDefaultLineLabelStyles(); + private Map lineLabelRenderers = getDefaultLineLabelRenderers(); public static class LineLabelRenderer { @@ -1051,8 +1065,8 @@ public void setLineExtensionRight(float lineExtensionRight) { this.lineExtensionRight = lineExtensionRight; } - protected HashMap getDefaultLineLabelStyles() { - HashMap defaults = new HashMap<>(); + protected Map getDefaultLineLabelStyles() { + EnumMap defaults = new EnumMap<>(Edge.class); defaults.put(Edge.TOP, new LineLabelStyle()); defaults.put(Edge.BOTTOM, new LineLabelStyle()); defaults.put(Edge.LEFT, new LineLabelStyle()); @@ -1060,8 +1074,8 @@ protected HashMap getDefaultLineLabelStyles() { return defaults; } - protected HashMap getDefaultLineLabelRenderers() { - HashMap defaults = new HashMap<>(); + protected Map getDefaultLineLabelRenderers() { + EnumMap defaults = new EnumMap<>(Edge.class); defaults.put(Edge.TOP, new LineLabelRenderer()); defaults.put(Edge.BOTTOM, new LineLabelRenderer()); defaults.put(Edge.LEFT, new LineLabelRenderer()); @@ -1145,17 +1159,15 @@ public boolean isLineLabelEnabled(Edge position) { } public void setLineLabelEdges(Edge... positions) { - Set positionSet = new HashSet<>(); + EnumSet positionSet = EnumSet.noneOf(Edge.class); if(positions != null) { - for(Edge position : positions) { - positionSet.add(position); - } + Collections.addAll(positionSet, positions); } - setLineLabelEdges(positionSet); + this.lineLabelEdges = positionSet; } - public void setLineLabelEdges(Set positions) { - this.lineLabelEdges = positions; + public void setLineLabelEdges(Collection positions) { + this.lineLabelEdges = EnumSet.copyOf(positions); } protected void setLineLabelEdges(int bitfield) { diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java b/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java index 33769886..9c131fa6 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java @@ -25,9 +25,15 @@ import android.support.annotation.NonNull; import android.util.AttributeSet; -import com.androidplot.*; -import com.androidplot.ui.*; +import com.androidplot.Plot; +import com.androidplot.R; +import com.androidplot.ui.Anchor; +import com.androidplot.ui.DynamicTableModel; +import com.androidplot.ui.HorizontalPositioning; +import com.androidplot.ui.Size; +import com.androidplot.ui.SizeMode; import com.androidplot.ui.TextOrientation; +import com.androidplot.ui.VerticalPositioning; import com.androidplot.ui.widget.TextLabelWidget; import com.androidplot.util.AttrUtils; import com.androidplot.util.PixelUtils; @@ -581,7 +587,7 @@ protected Number getCalculatedLowerBoundary(BoundaryMode mode, Number previousMi * @param min * @param max */ - private Number applyUserMinMax(Number value, Number min, Number max) { + private static Number applyUserMinMax(Number value, Number min, Number max) { value = (((min == null) || (value == null) || (value.doubleValue() > min.doubleValue())) ? value : min); @@ -678,7 +684,7 @@ protected Number[] getOriginMinMax(BoundaryMode mode, Number origin, Number exte * @param y * @return */ - private double distance(double x, double y) { + private static double distance(double x, double y) { if (x > y) { return x - y; } else { diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYSeriesFormatter.java b/androidplot-core/src/main/java/com/androidplot/xy/XYSeriesFormatter.java index 0c18d2e9..27924ab1 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYSeriesFormatter.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYSeriesFormatter.java @@ -16,7 +16,7 @@ package com.androidplot.xy; -import android.content.*; +import android.content.Context; import com.androidplot.ui.Formatter; import com.androidplot.util.LayerHash; @@ -28,7 +28,7 @@ public abstract class XYSeriesFormatter Date: Thu, 1 Jun 2017 07:25:51 -0500 Subject: [PATCH 24/81] Bounds not calculated properly for FastXYSeries (#49) * uprev Gradle build tools 2.3.1 -> 2.3.2 * FastNumber's constructor is now private. Added safe static initializer; FastNumber.orNull which returns null if a null Number is passed into it. * #45 SeriesUtils now falls back to default min/max calculation on FastXYSeries whose bounds fall completely within the owning XYPlot's constraints. --- .../src/main/java/com/androidplot/Region.java | 6 +- .../java/com/androidplot/util/FastNumber.java | 19 +++++- .../com/androidplot/util/SeriesUtils.java | 23 +++---- .../java/com/androidplot/xy/FastXYSeries.java | 4 ++ .../xy/FixedSizeEditableXYSeries.java | 10 +-- .../com/androidplot/xy/XYConstraints.java | 64 ++++++++++------- .../main/java/com/androidplot/xy/XYPlot.java | 44 ++++++------ .../com/androidplot/util/FastNumberTest.java | 17 ++--- .../com/androidplot/util/SeriesUtilsTest.java | 68 +++++++++++++++++-- build.gradle | 2 +- demoapp-wearable/build.gradle | 2 +- 11 files changed, 172 insertions(+), 87 deletions(-) diff --git a/androidplot-core/src/main/java/com/androidplot/Region.java b/androidplot-core/src/main/java/com/androidplot/Region.java index 3f67bb09..f22f9115 100644 --- a/androidplot-core/src/main/java/com/androidplot/Region.java +++ b/androidplot-core/src/main/java/com/androidplot/Region.java @@ -71,7 +71,7 @@ public Number length() { Number l = getMax() == null || getMin() == null ? null : getMax().doubleValue() - getMin().doubleValue(); if(l != null) { - cachedLength = new FastNumber(l); + cachedLength = FastNumber.orNull(l); } } return cachedLength; @@ -216,7 +216,7 @@ public void setMin(Number min) { this.min = null; } } else if (this.min == null || !this.min.equals(min)) { - this.min = new FastNumber(min); + this.min = FastNumber.orNull(min); } } @@ -238,7 +238,7 @@ public void setMax(Number max) { this.max = null; } } else if (this.max == null || !this.max.equals(max)) { - this.max = new FastNumber(max); + this.max = FastNumber.orNull(max); } } diff --git a/androidplot-core/src/main/java/com/androidplot/util/FastNumber.java b/androidplot-core/src/main/java/com/androidplot/util/FastNumber.java index 4b113b5d..72e8a25d 100644 --- a/androidplot-core/src/main/java/com/androidplot/util/FastNumber.java +++ b/androidplot-core/src/main/java/com/androidplot/util/FastNumber.java @@ -8,8 +8,7 @@ */ public class FastNumber extends Number { - @NonNull - private final Number number; + @NonNull private final Number number; private boolean hasDoublePrimitive; private boolean hasFloatPrimitive; private boolean hasIntPrimitive; @@ -18,7 +17,21 @@ public class FastNumber extends Number { private float floatPrimitive; private int intPrimitive; - public FastNumber(@NonNull Number number) { + /** + * Safe-instantiator of FastNumber; returns a null result if the input Number is also null. + * @param number + * @return + */ + public static FastNumber orNull(@NonNull Number number) { + if(number == null) { + return null; + } else { + return new FastNumber(number); + } + } + + private FastNumber(@NonNull Number number) { + //noinspection ConstantConditions //in case someone ignores the @NonNull annotation if (number == null) { throw new IllegalArgumentException("number parameter cannot be null"); diff --git a/androidplot-core/src/main/java/com/androidplot/util/SeriesUtils.java b/androidplot-core/src/main/java/com/androidplot/util/SeriesUtils.java index e74a8f3b..bd265582 100644 --- a/androidplot-core/src/main/java/com/androidplot/util/SeriesUtils.java +++ b/androidplot-core/src/main/java/com/androidplot/util/SeriesUtils.java @@ -65,7 +65,6 @@ public static Region minMaxY(XYSeries... seriesList) { * @since 0.9.7 */ public static RectRegion minMax(XYConstraints constraints, List seriesList) { - // TODO: this is inefficient...clean it up! return minMax(constraints, seriesList.toArray(new XYSeries[seriesList.size()])); } @@ -86,23 +85,17 @@ public static RectRegion minMax(XYConstraints constraints, XYSeries... seriesArr for (XYSeries series : seriesArray) { // if this is an advanced xy series then minMax have already been calculated for us: - if(series instanceof FastXYSeries) { - final RectRegion seriesBounds = ((FastXYSeries) series).minMax(); - if (seriesBounds == null) { + boolean isPreCalculated = false; + if (series instanceof FastXYSeries) { + final RectRegion b = ((FastXYSeries) series).minMax(); + if(b == null) { continue; } - if(constraints == null) { - bounds.union(seriesBounds); - } else { - if (constraints.contains(seriesBounds.getMinX(), seriesBounds.getMinY())) { - bounds.union(seriesBounds.getMinX(), seriesBounds.getMinY()); - } - if (constraints.contains(seriesBounds.getMaxX(), seriesBounds.getMaxY())) { - bounds.union(seriesBounds.getMaxX(), seriesBounds.getMaxY()); - } + if(constraints == null || constraints.contains(b)) { + bounds.union(b); } - - } else if (series.size() > 0) { + } + if (!isPreCalculated && series.size() > 0) { for (int i = 0; i < series.size(); i++) { final Number xi = series.getX(i); final Number yi = series.getY(i); diff --git a/androidplot-core/src/main/java/com/androidplot/xy/FastXYSeries.java b/androidplot-core/src/main/java/com/androidplot/xy/FastXYSeries.java index 4faa4d54..de5d534b 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/FastXYSeries.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/FastXYSeries.java @@ -3,6 +3,10 @@ /** * An implementation of {@link XYSeries} that defines additional methods to speed up rendering by * giving a hint to the renderer about the min/max values contained in the series. + * + * Note that these hints can only be leveraged if the containing XYPlot's constraints completely + * contain the FastXYSeries min/max values. If this condition is not met then XYPlot falls back + * to manually determining the min/max values of the series that exist within the defined constraints. */ public interface FastXYSeries extends XYSeries { diff --git a/androidplot-core/src/main/java/com/androidplot/xy/FixedSizeEditableXYSeries.java b/androidplot-core/src/main/java/com/androidplot/xy/FixedSizeEditableXYSeries.java index 03e328c9..0e6ea1a1 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/FixedSizeEditableXYSeries.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/FixedSizeEditableXYSeries.java @@ -1,6 +1,7 @@ package com.androidplot.xy; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import com.androidplot.util.FastNumber; @@ -19,6 +20,7 @@ public class FixedSizeEditableXYSeries implements EditableXYSeries { @NonNull private List xVals = new ArrayList<>(); + @NonNull private List yVals = new ArrayList<>(); private String title; @@ -29,13 +31,13 @@ public FixedSizeEditableXYSeries(String title, int size) { } @Override - public void setX(@NonNull Number x, int index) { - xVals.set(index, new FastNumber(x)); + public void setX(@Nullable Number x, int index) { + xVals.set(index, FastNumber.orNull(x)); } @Override - public void setY(@NonNull Number y, int index) { - yVals.set(index, new FastNumber(y)); + public void setY(@Nullable Number y, int index) { + yVals.set(index, FastNumber.orNull(y)); } /** diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYConstraints.java b/androidplot-core/src/main/java/com/androidplot/xy/XYConstraints.java index 705ae635..58338264 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYConstraints.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYConstraints.java @@ -16,6 +16,9 @@ package com.androidplot.xy; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + /** * Calculates the min/max constraints for an xy plane. * @@ -45,13 +48,18 @@ public XYConstraints() { this(null, null, null, null); } - public XYConstraints(Number minX, Number maxX, Number minY, Number maxY) { + public XYConstraints(@Nullable Number minX, @Nullable Number maxX, @Nullable Number minY, @Nullable Number maxY) { this.minX = minX; this.minY = minY; this.maxX = maxX; this.maxY = maxY; } + public boolean contains(@NonNull RectRegion rectRegion) { + return contains(rectRegion.getMinY(), rectRegion.getMinY()) + && contains(rectRegion.getMaxX(), rectRegion.getMaxY()); + } + public boolean contains(Number x, Number y) { if (x == null || y == null) { // this is essentially an invisible point: @@ -80,86 +88,96 @@ public boolean contains(Number x, Number y) { return true; } + @Nullable public Number getMinX() { return minX; } + @Nullable public Number getMaxX() { return maxX; } + @Nullable public Number getMinY() { return minY; } + @Nullable public Number getMaxY() { return maxY; } + public void setMinX(@Nullable Number minX) { + this.minX = minX; + } + + public void setMaxX(@Nullable Number maxX) { + this.maxX = maxX; + } + + public void setMinY(@Nullable Number minY) { + this.minY = minY; + } + + public void setMaxY(@Nullable Number maxY) { + this.maxY = maxY; + } + + @NonNull public XYFramingModel getDomainFramingModel() { return domainFramingModel; } - public void setDomainFramingModel(XYFramingModel domainFramingModel) { + public void setDomainFramingModel(@NonNull XYFramingModel domainFramingModel) { this.domainFramingModel = domainFramingModel; } + @NonNull public XYFramingModel getRangeFramingModel() { return rangeFramingModel; } - public void setRangeFramingModel(XYFramingModel rangeFramingModel) { + public void setRangeFramingModel(@NonNull XYFramingModel rangeFramingModel) { this.rangeFramingModel = rangeFramingModel; } + @NonNull public BoundaryMode getDomainUpperBoundaryMode() { return domainUpperBoundaryMode; } - public void setDomainUpperBoundaryMode(BoundaryMode domainUpperBoundaryMode) { + public void setDomainUpperBoundaryMode(@NonNull BoundaryMode domainUpperBoundaryMode) { this.domainUpperBoundaryMode = domainUpperBoundaryMode; } + @NonNull public BoundaryMode getDomainLowerBoundaryMode() { return domainLowerBoundaryMode; } - public void setDomainLowerBoundaryMode(BoundaryMode domainLowerBoundaryMode) { + public void setDomainLowerBoundaryMode(@NonNull BoundaryMode domainLowerBoundaryMode) { this.domainLowerBoundaryMode = domainLowerBoundaryMode; } + @NonNull public BoundaryMode getRangeUpperBoundaryMode() { return rangeUpperBoundaryMode; } - public void setRangeUpperBoundaryMode(BoundaryMode rangeUpperBoundaryMode) { + public void setRangeUpperBoundaryMode(@NonNull BoundaryMode rangeUpperBoundaryMode) { this.rangeUpperBoundaryMode = rangeUpperBoundaryMode; } + @NonNull public BoundaryMode getRangeLowerBoundaryMode() { return rangeLowerBoundaryMode; } - public void setRangeLowerBoundaryMode(BoundaryMode rangeLowerBoundaryMode) { + public void setRangeLowerBoundaryMode(@NonNull BoundaryMode rangeLowerBoundaryMode) { this.rangeLowerBoundaryMode = rangeLowerBoundaryMode; } - public void setMinX(Number minX) { - this.minX = minX; - } - - public void setMaxX(Number maxX) { - this.maxX = maxX; - } - - public void setMinY(Number minY) { - this.minY = minY; - } - - public void setMaxY(Number maxY) { - this.maxY = maxY; - } - @Override public String toString() { return "XYConstraints{" + "domainFramingModel=" + domainFramingModel + diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java b/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java index 9c131fa6..15e8bbc2 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java @@ -694,15 +694,15 @@ private static double distance(double x, double y) { public void updateDomainMinMaxForOriginModel() { double origin = userDomainOrigin.doubleValue(); - double maxXDelta = distance(bounds.getMaxX().doubleValue(), origin); - double minXDelta = distance(bounds.getMinX().doubleValue(), origin); - double delta = maxXDelta > minXDelta ? maxXDelta : minXDelta; - double dlb = origin - delta; - double dub = origin + delta; + double maxDelta = distance(bounds.getMaxX().doubleValue(), origin); + double minDelta = distance(bounds.getMinX().doubleValue(), origin); + double delta = maxDelta > minDelta ? maxDelta : minDelta; + double lowerBoundary = origin - delta; + double upperBoundary = origin + delta; switch (domainOriginBoundaryMode) { case AUTO: - bounds.setMinX(dlb); - bounds.setMaxX(dub); + bounds.setMinX(lowerBoundary); + bounds.setMaxX(upperBoundary); break; // if fixed, then the value already exists within "user" vals. @@ -710,28 +710,28 @@ public void updateDomainMinMaxForOriginModel() { break; case GROW: { - if (prevMinX == null || dlb < prevMinX.doubleValue()) { - bounds.setMinX(dlb); + if (prevMinX == null || lowerBoundary < prevMinX.doubleValue()) { + bounds.setMinX(lowerBoundary); } else { bounds.setMinX(prevMinX); } - if (prevMaxX == null || dub > prevMaxX.doubleValue()) { - bounds.setMaxX(dub); + if (prevMaxX == null || upperBoundary > prevMaxX.doubleValue()) { + bounds.setMaxX(upperBoundary); } else { bounds.setMaxX(prevMaxX); } } break; case SHRINK: - if (prevMinX == null || dlb > prevMinX.doubleValue()) { - bounds.setMinX(dlb); + if (prevMinX == null || lowerBoundary > prevMinX.doubleValue()) { + bounds.setMinX(lowerBoundary); } else { bounds.setMinX(prevMinX); } - if (prevMaxX == null || dub < prevMaxX.doubleValue()) { - bounds.setMaxX(dub); + if (prevMaxX == null || upperBoundary < prevMaxX.doubleValue()) { + bounds.setMaxX(upperBoundary); } else { bounds.setMaxX(prevMaxX); } @@ -745,14 +745,14 @@ public void updateRangeMinMaxForOriginModel() { switch (rangeOriginBoundaryMode) { case AUTO: double origin = userRangeOrigin.doubleValue(); - double maxYDelta = distance(bounds.getMaxY().doubleValue(), origin); - double minYDelta = distance(bounds.getMinY().doubleValue(), origin); - if (maxYDelta > minYDelta) { - bounds.setMinY(origin - maxYDelta); - bounds.setMaxY(origin + maxYDelta); + double maxDelta = distance(bounds.getMaxY().doubleValue(), origin); + double minDelta = distance(bounds.getMinY().doubleValue(), origin); + if (maxDelta > minDelta) { + bounds.setMinY(origin - maxDelta); + bounds.setMaxY(origin + maxDelta); } else { - bounds.setMinY(origin - minYDelta); - bounds.setMaxY(origin + minYDelta); + bounds.setMinY(origin - minDelta); + bounds.setMaxY(origin + minDelta); } break; case FIXED: diff --git a/androidplot-core/src/test/java/com/androidplot/util/FastNumberTest.java b/androidplot-core/src/test/java/com/androidplot/util/FastNumberTest.java index a029600a..8bca2a38 100644 --- a/androidplot-core/src/test/java/com/androidplot/util/FastNumberTest.java +++ b/androidplot-core/src/test/java/com/androidplot/util/FastNumberTest.java @@ -9,6 +9,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @SuppressWarnings({"UnnecessaryBoxing", "ObjectEqualsNull", "EqualsBetweenInconvertibleTypes", "EqualsWithItself", "NumberEquality"}) @@ -84,16 +85,16 @@ public void tearDown() throws Exception { } - @Test(expected = IllegalArgumentException.class) - public void constructor_throwsException_ifNumberIsNull() { + @Test + public void orNull_returnsNull_ifNullNumber() { //noinspection ConstantConditions - new FastNumber(null); + assertNull(FastNumber.orNull(null)); } @Test public void equals_returnsTrue_ifFromSameNumberInstance() { for (Number number : NUMBERS) { - assertTrue("Equality test failed on " + number, new FastNumber(number).equals(new FastNumber(number))); + assertTrue("Equality test failed on " + number, FastNumber.orNull(number).equals(FastNumber.orNull(number))); } } @@ -102,7 +103,7 @@ public void equals_returnsTrue_ifFromSameNumber() { assertEquals("misconfigured test values", NUMBERS.length, NUMBERS_CLONE.length); for (int i = 0; i < NUMBERS.length; i++) { assertEquals("misconfigured test values", NUMBERS[i], NUMBERS_CLONE[i]); - assertTrue(new FastNumber(NUMBERS[i]).equals(new FastNumber(NUMBERS_CLONE[i]))); + assertTrue(FastNumber.orNull(NUMBERS[i]).equals(FastNumber.orNull(NUMBERS_CLONE[i]))); } } @@ -114,7 +115,7 @@ public void equals_returnsFalse_ifNumberIsDifferent() { continue; } assertNotEquals("duplicate test values at index " + i + " and " + j, NUMBERS[i], NUMBERS[j]); - assertFalse(new FastNumber(NUMBERS[i]).equals(new FastNumber(NUMBERS[j]))); + assertFalse(FastNumber.orNull(NUMBERS[i]).equals(FastNumber.orNull(NUMBERS[j]))); } } } @@ -123,8 +124,8 @@ public void equals_returnsFalse_ifNumberIsDifferent() { public void hashCode_isEqual_ifInstanceIsEqual() { for (Number number : NUMBERS) { for (Number number2 : NUMBERS) { - FastNumber fastNumber1 = new FastNumber(number); - FastNumber fastNumber2 = new FastNumber(number2); + FastNumber fastNumber1 = FastNumber.orNull(number); + FastNumber fastNumber2 = FastNumber.orNull(number2); if (fastNumber1.equals(fastNumber2)) { assertEquals(fastNumber1.hashCode(), fastNumber2.hashCode()); } diff --git a/androidplot-core/src/test/java/com/androidplot/util/SeriesUtilsTest.java b/androidplot-core/src/test/java/com/androidplot/util/SeriesUtilsTest.java index 4ff129bc..df9014cd 100644 --- a/androidplot-core/src/test/java/com/androidplot/util/SeriesUtilsTest.java +++ b/androidplot-core/src/test/java/com/androidplot/util/SeriesUtilsTest.java @@ -51,7 +51,7 @@ public void tearDown() throws Exception { } @Test - public void testSeriesMinMax() { + public void minMax_onSimpleXYSeries_calculatesExpectedRegion() { SimpleXYSeries series = new SimpleXYSeries(LINEAR, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, null); RectRegion minMax = SeriesUtils.minMax(series); assertEquals(0, minMax.getMinX().doubleValue(), 0); @@ -103,14 +103,68 @@ public void testSeriesMinMax() { } @Test - public void minMax_usesSeriesMinMax_onFastXYSeries() { + public void minMax_onFastXYSeries_usesSeriesMinMax() { FastXYSeries series = mock(FastXYSeries.class); SeriesUtils.minMax(series); verify(series).minMax(); } @Test - public void testListMinMax() { + public void minMax_calculatesExpectedRegion_onFastXYSeriesWithLayoutConstraints() { + FastXYSeries series = new FastXYSeries() { + + // create a couple arrays of y-values to plot: + final ArrayList times = new ArrayList<>(); + final ArrayList values = new ArrayList<>(); + + { + for (int i = 0; i < 10; i++) { + times.add(i); + values.add(i); + } + } + + @Override + public int size() { + return times.size(); + } + + @Override + public Number getX(int index) { + return times.get(index); + } + + @Override + public Number getY(int index) { + return values.get(index); + } + + @Override + public String getTitle() { + return "Isaac's crazy thing"; + } + + @Override + public RectRegion minMax() { + return new RectRegion(0, 10, 0, 10); + } + }; + + final XYConstraints constraints = new XYConstraints(); + constraints.setDomainLowerBoundaryMode(BoundaryMode.FIXED); + constraints.setDomainUpperBoundaryMode(BoundaryMode.FIXED); + constraints.setMinX(5); + constraints.setMaxX(9); + + final RectRegion result = SeriesUtils.minMax(constraints, series); + assertEquals(5d, result.getMinX().doubleValue()); + assertEquals(9d, result.getMaxX().doubleValue()); + assertEquals(5d, result.getMinY().doubleValue()); + assertEquals(9d, result.getMaxY().doubleValue()); + } + + @Test + public void minMax_onSeriesList_producesAggregateResult() { Region minMax = SeriesUtils.minMax(LINEAR); assertEquals(1, minMax.getMin().doubleValue(), 0); assertEquals(8, minMax.getMax().doubleValue(), 0); @@ -141,7 +195,7 @@ public void testListMinMax() { } @Test - public void testGetNullRegion() { + public void getNullRegion_producesExpectedResult() { XYSeries s1 = new SimpleXYSeries( SimpleXYSeries.ArrayFormat.XY_VALS_INTERLEAVED, "s1", 0, 0, // 0 @@ -176,7 +230,7 @@ public void testGetNullRegion() { } @Test - public void testIboundsMin() { + public void iBoundsMin_findsMin() { XYSeries s1 = new SimpleXYSeries( Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), @@ -203,7 +257,7 @@ public void testIboundsMin() { } @Test - public void testIboundsMax() { + public void iBoundsMax_findsMax() { XYSeries s1 = new SimpleXYSeries( Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), @@ -230,7 +284,7 @@ public void testIboundsMax() { } @Test - public void testIbounds() { + public void iBounds_findsMinMax() { FastXYSeries series = mock(FastXYSeries.class); when(series.size()).thenReturn(3); when(series.getX(0)).thenReturn(0); diff --git a/build.gradle b/build.gradle index dd84178e..f6deec86 100644 --- a/build.gradle +++ b/build.gradle @@ -38,7 +38,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.3.1' + classpath 'com.android.tools.build:gradle:2.3.2' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7' classpath 'com.vanniktech:gradle-android-junit-jacoco-plugin:0.5.0' diff --git a/demoapp-wearable/build.gradle b/demoapp-wearable/build.gradle index dd1d8926..37406aaa 100644 --- a/demoapp-wearable/build.gradle +++ b/demoapp-wearable/build.gradle @@ -19,7 +19,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.1' + classpath 'com.android.tools.build:gradle:2.3.2' } } apply plugin: 'com.android.application' From 28e5b312c1fc9d7b608472f8815cdee054e46008 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Wed, 12 Jul 2017 08:39:03 -0500 Subject: [PATCH 25/81] Pie legend widget (#54) Adds legend support to PieChart and refactors legend functionality into abstract class LegendWidget. Also updates docs / unit tests. --- .../java/com/androidplot/pie/PieChart.java | 45 +++- .../com/androidplot/pie/PieLegendItem.java | 26 ++ .../com/androidplot/pie/PieLegendWidget.java | 41 +++ .../java/com/androidplot/ui/RenderStack.java | 2 +- .../com/androidplot/ui/widget/LegendItem.java | 13 + .../ui/widget/LegendItemOrganizer.java | 9 + .../androidplot/ui/widget/LegendWidget.java | 197 +++++++++++++++ .../java/com/androidplot/xy/XYLegendItem.java | 28 ++ .../com/androidplot/xy/XYLegendWidget.java | 239 ++++-------------- .../main/java/com/androidplot/xy/XYPlot.java | 6 +- .../com/androidplot/ui/RenderStackTest.java | 52 ++++ .../androidplot/xy/XYLegendWidgetTest.java | 120 ++++++--- .../demos/SimplePieChartActivity.java | 29 +-- docs/legend.md | 59 +++++ docs/release_notes.md | 20 +- docs/xyplot.md | 41 +-- gradle/wrapper/gradle-wrapper.properties | 2 +- 17 files changed, 618 insertions(+), 311 deletions(-) create mode 100644 androidplot-core/src/main/java/com/androidplot/pie/PieLegendItem.java create mode 100644 androidplot-core/src/main/java/com/androidplot/pie/PieLegendWidget.java create mode 100644 androidplot-core/src/main/java/com/androidplot/ui/widget/LegendItem.java create mode 100644 androidplot-core/src/main/java/com/androidplot/ui/widget/LegendItemOrganizer.java create mode 100644 androidplot-core/src/main/java/com/androidplot/ui/widget/LegendWidget.java create mode 100644 androidplot-core/src/main/java/com/androidplot/xy/XYLegendItem.java create mode 100644 androidplot-core/src/test/java/com/androidplot/ui/RenderStackTest.java create mode 100644 docs/legend.md diff --git a/androidplot-core/src/main/java/com/androidplot/pie/PieChart.java b/androidplot-core/src/main/java/com/androidplot/pie/PieChart.java index 615efaaa..35e5e416 100644 --- a/androidplot-core/src/main/java/com/androidplot/pie/PieChart.java +++ b/androidplot-core/src/main/java/com/androidplot/pie/PieChart.java @@ -37,15 +37,18 @@ public class PieChart extends Plot { + + private PieChart pieChart; + + public PieLegendWidget(LayoutManager layoutManager, PieChart pieChart, + Size widgetSize, + TableModel tableModel, + Size iconSize) { + super(tableModel, layoutManager, widgetSize, iconSize); + this.pieChart = pieChart; + } + + @Override + protected void drawIcon(@NonNull Canvas canvas, @NonNull RectF iconRect, @NonNull PieLegendItem item) { + canvas.drawRect(iconRect, item.formatter.getFillPaint()); + } + + @Override + protected List getLegendItems() { + final List legendItems = new ArrayList<>(); + for(SeriesBundle item : pieChart.getRegistry().getLegendEnabledItems()) { + legendItems.add(new PieLegendItem(item.getSeries(), item.getFormatter())); + } + return legendItems; + } +} diff --git a/androidplot-core/src/main/java/com/androidplot/ui/RenderStack.java b/androidplot-core/src/main/java/com/androidplot/ui/RenderStack.java index d9b9d812..99227563 100644 --- a/androidplot-core/src/main/java/com/androidplot/ui/RenderStack.java +++ b/androidplot-core/src/main/java/com/androidplot/ui/RenderStack.java @@ -23,7 +23,7 @@ import java.util.List; /** - * A stack of series to be rendered. The stack order is immutable but individual elements may be + * A stack of series to be rendered. The stack order is immutable but individual elements may be * manipulated via the public methods of {@link RenderStack.StackElement}. */ public class RenderStack { diff --git a/androidplot-core/src/main/java/com/androidplot/ui/widget/LegendItem.java b/androidplot-core/src/main/java/com/androidplot/ui/widget/LegendItem.java new file mode 100644 index 00000000..5bb428ac --- /dev/null +++ b/androidplot-core/src/main/java/com/androidplot/ui/widget/LegendItem.java @@ -0,0 +1,13 @@ +package com.androidplot.ui.widget; + +/** + * An item to be displayed by {@link LegendWidget}. + */ +public interface LegendItem { + + /** + * + * @return The user facing label for this item. + */ + String getTitle(); +} diff --git a/androidplot-core/src/main/java/com/androidplot/ui/widget/LegendItemOrganizer.java b/androidplot-core/src/main/java/com/androidplot/ui/widget/LegendItemOrganizer.java new file mode 100644 index 00000000..536ee9d8 --- /dev/null +++ b/androidplot-core/src/main/java/com/androidplot/ui/widget/LegendItemOrganizer.java @@ -0,0 +1,9 @@ +package com.androidplot.ui.widget; + +import java.util.List; + + +public interface LegendItemOrganizer { + + void organize(List items); +} diff --git a/androidplot-core/src/main/java/com/androidplot/ui/widget/LegendWidget.java b/androidplot-core/src/main/java/com/androidplot/ui/widget/LegendWidget.java new file mode 100644 index 00000000..69e41407 --- /dev/null +++ b/androidplot-core/src/main/java/com/androidplot/ui/widget/LegendWidget.java @@ -0,0 +1,197 @@ +package com.androidplot.ui.widget; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.RectF; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.androidplot.exception.PlotRenderException; +import com.androidplot.ui.LayoutManager; +import com.androidplot.ui.Size; +import com.androidplot.ui.TableModel; +import com.androidplot.util.FontUtils; +import com.androidplot.util.PixelUtils; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +/** + * Provides core functionality for displaying a legend widget within a {@link com.androidplot.Plot}. + * @param + */ +public abstract class LegendWidget extends Widget { + + private static final float DEFAULT_TEXT_SIZE_DP = 20; + + private TableModel tableModel; + private Size iconSize; + + private Paint textPaint; + private Paint iconBackgroundPaint; + private Paint iconBorderPaint; + + private boolean drawIconBackgroundEnabled = true; + private boolean drawIconBorderEnabled = true; + + private Comparator legendItemComparator; + + { + textPaint = new Paint(); + textPaint.setColor(Color.LTGRAY); + textPaint.setTextSize(PixelUtils.spToPix(DEFAULT_TEXT_SIZE_DP)); + textPaint.setAntiAlias(true); + + iconBackgroundPaint = new Paint(); + iconBackgroundPaint.setColor(Color.BLACK); + + iconBorderPaint = new Paint(); + iconBorderPaint.setColor(Color.TRANSPARENT); + iconBorderPaint.setStyle(Paint.Style.STROKE); + } + + + public LegendWidget(@NonNull TableModel tableModel, @NonNull LayoutManager layoutManager, + @NonNull Size size, @NonNull Size iconSize) { + super(layoutManager, size); + setTableModel(tableModel); + this.iconSize = iconSize; + } + + @Override + protected void doOnDraw(Canvas canvas, RectF widgetRect) throws PlotRenderException { + final List items = getLegendItems(); + if(legendItemComparator != null) { + Collections.sort(items, legendItemComparator); + } + final Iterator cellRectIterator = tableModel.getIterator(widgetRect, items.size()); + for(ItemT item : items) { + final RectF cellRect = cellRectIterator.next(); + final RectF iconRect = getIconRect(cellRect); + beginDrawingCell(canvas, iconRect); + drawItem(canvas, iconRect, item); + finishDrawingCell(canvas, cellRect, iconRect, item); + } + } + + protected void drawItem(@NonNull Canvas canvas, @NonNull RectF iconRect, @NonNull ItemT item) { + drawIcon(canvas, iconRect, item); + } + + /** + * Draw the icon representing the legend item + * @param canvas + * @param iconRect The space to be occupied by the icon. + * @param item + */ + protected abstract void drawIcon(@NonNull Canvas canvas, @NonNull RectF iconRect, @NonNull ItemT item); + + /** + * + * @return The list of legend items to be drawn. This is used to calculate table dimensions etc. + */ + protected abstract List getLegendItems(); + + private RectF getIconRect(RectF cellRect) { + float cellRectCenterY = cellRect.top + (cellRect.height()/2); + RectF iconRect = iconSize.getRectF(cellRect); + + // center the icon rect vertically + float centeredIconOriginY = cellRectCenterY - (iconRect.height()/2); + iconRect.offsetTo(cellRect.left + 1, centeredIconOriginY); + return iconRect; + } + + /** + * Done at the start of rendering a new cell. Whatever is drawn here will be beneath the rest + * of the cell content; typically used to draw backgrounds. + * @param canvas + * @param iconRect + */ + protected void beginDrawingCell(Canvas canvas, RectF iconRect) { + + if(drawIconBackgroundEnabled && iconBackgroundPaint != null) { + canvas.drawRect(iconRect, iconBackgroundPaint); + } + } + + /** + * Done at the end of rendering a new cell. Whatever is drawn here will be on top of + * the rest of the cell content; typically used to draw borders and text. + * @param canvas + * @param cellRect + * @param iconRect + * @param legendItem + */ + protected void finishDrawingCell(Canvas canvas, RectF cellRect, RectF iconRect, LegendItem legendItem) { + + if(drawIconBorderEnabled && iconBorderPaint != null) { + canvas.drawRect(iconRect, iconBorderPaint); + } + + float centeredTextOriginY = getRectCenterY(cellRect) + (FontUtils.getFontHeight(textPaint)/2); + + if (textPaint.getTextAlign().equals(Paint.Align.RIGHT)) { + canvas.drawText(legendItem.getTitle(), iconRect.left - 2, centeredTextOriginY, textPaint); + } else { + canvas.drawText(legendItem.getTitle(), iconRect.right + 2, centeredTextOriginY, textPaint); + } + } + + protected static float getRectCenterY(RectF cellRect) { + return cellRect.top + (cellRect.height()/2); + } + + public synchronized void setTableModel(TableModel tableModel) { + this.tableModel = tableModel; + } + + public Paint getTextPaint() { + return textPaint; + } + + public void setTextPaint(Paint textPaint) { + this.textPaint = textPaint; + } + + public boolean isDrawIconBackgroundEnabled() { + return drawIconBackgroundEnabled; + } + + public void setDrawIconBackgroundEnabled(boolean drawIconBackgroundEnabled) { + this.drawIconBackgroundEnabled = drawIconBackgroundEnabled; + } + + public boolean isDrawIconBorderEnabled() { + return drawIconBorderEnabled; + } + + public void setDrawIconBorderEnabled(boolean drawIconBorderEnabled) { + this.drawIconBorderEnabled = drawIconBorderEnabled; + } + + public Size getIconSize() { + return iconSize; + } + + public void setIconSize(Size iconSize) { + this.iconSize = iconSize; + } + + public Comparator getLegendItemComparator() { + return legendItemComparator; + } + + /** + * Set a scheme for sorting the display order or legend items. By default no sorting is applied + * and {@link com.androidplot.Series} items typically appear in the order which the series was + * added to the {@link com.androidplot.Plot}. + * @param legendItemComparator + */ + public void setLegendItemComparator(Comparator legendItemComparator) { + this.legendItemComparator = legendItemComparator; + } +} diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYLegendItem.java b/androidplot-core/src/main/java/com/androidplot/xy/XYLegendItem.java new file mode 100644 index 00000000..1fd10ebd --- /dev/null +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYLegendItem.java @@ -0,0 +1,28 @@ +package com.androidplot.xy; + +import android.support.annotation.NonNull; + +import com.androidplot.ui.widget.LegendItem; + +public class XYLegendItem implements LegendItem { + + public enum Type { + SERIES, + REGION + } + + public final Type type; + public final Object item; + private final String text; + + public XYLegendItem(@NonNull Type cellType, @NonNull Object item, @NonNull String text) { + this.type = cellType; + this.item = item; + this.text = text; + } + + @Override + public String getTitle() { + return this.text; + } +} diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYLegendWidget.java b/androidplot-core/src/main/java/com/androidplot/xy/XYLegendWidget.java index e5b981c5..f08783cb 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYLegendWidget.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYLegendWidget.java @@ -17,233 +17,80 @@ package com.androidplot.xy; import android.graphics.*; +import android.support.annotation.NonNull; + import com.androidplot.ui.LayoutManager; import com.androidplot.ui.SeriesBundle; import com.androidplot.ui.Size; import com.androidplot.ui.TableModel; -import com.androidplot.ui.widget.Widget; -import com.androidplot.util.FontUtils; +import com.androidplot.ui.widget.LegendWidget; -import java.util.*; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Hashtable; +import java.util.List; +import java.util.Map.Entry; /** * Displays a legend for each series added to the owning {@link XYPlot}. */ -public class XYLegendWidget extends Widget { - - /** - * This class is of no use outside of XYLegendWidget. It's just used to alphabetically sort - * Region legend entries. - */ - private static class RegionEntryComparator implements Comparator> { - @Override - public int compare(Map.Entry o1, Map.Entry o2) { - return o1.getValue().compareTo(o2.getValue()); - } - } - - private enum CellType { - SERIES, - REGION - } +public class XYLegendWidget extends LegendWidget { private XYPlot plot; - //private float iconWidth = 12; - private Paint textPaint; - private Paint iconBorderPaint; - private TableModel tableModel; - private boolean drawIconBackgroundEnabled = true; - private boolean drawIconBorderEnabled = true; - - private Size iconSize; - private static final RegionEntryComparator regionEntryComparator = new RegionEntryComparator(); - //private RectF iconRect = new RectF(0, 0, ICON_WIDTH_DEFAULT, ICON_HEIGHT_DEFAULT); - - { - textPaint = new Paint(); - textPaint.setColor(Color.LTGRAY); - textPaint.setAntiAlias(true); - - iconBorderPaint = new Paint(); - iconBorderPaint.setStyle(Paint.Style.STROKE); - //regionEntryComparator = new RegionEntryComparator(); - } public XYLegendWidget(LayoutManager layoutManager, XYPlot plot, Size widgetSize, TableModel tableModel, Size iconSize) { - super(layoutManager, widgetSize); + super(tableModel, layoutManager, widgetSize, iconSize); this.plot = plot; - setTableModel(tableModel); - this.iconSize = iconSize; - } - - public synchronized void setTableModel(TableModel tableModel) { - this.tableModel = tableModel; - } - private RectF getIconRect(RectF cellRect) { - float cellRectCenterY = cellRect.top + (cellRect.height()/2); - RectF iconRect = iconSize.getRectF(cellRect); - - // center the icon rect vertically - float centeredIconOriginY = cellRectCenterY - (iconRect.height()/2); - iconRect.offsetTo(cellRect.left + 1, centeredIconOriginY); - return iconRect; - } - - private static float getRectCenterY(RectF cellRect) { - return cellRect.top + (cellRect.height()/2); - } - - private void beginDrawingCell(Canvas canvas, RectF iconRect) { - - Paint bgPaint = plot.getGraph().getGridBackgroundPaint(); - if(drawIconBackgroundEnabled && bgPaint != null) { - canvas.drawRect(iconRect, bgPaint); - } - } - - private void finishDrawingCell(Canvas canvas, RectF cellRect, RectF iconRect, String text) { - - Paint bgPaint = plot.getGraph().getGridBackgroundPaint(); - if(drawIconBorderEnabled && bgPaint != null) { - iconBorderPaint.setColor(bgPaint.getColor()); - canvas.drawRect(iconRect, iconBorderPaint); - } - - float centeredTextOriginY = getRectCenterY(cellRect) + (FontUtils.getFontHeight(textPaint)/2); - - if (textPaint.getTextAlign().equals(Paint.Align.RIGHT)) { - canvas.drawText(text, iconRect.left - 2, centeredTextOriginY, textPaint); - } else { - canvas.drawText(text, iconRect.right + 2, centeredTextOriginY, textPaint); - } + // Set a default comparator that sorts by type and then alphabetically + setLegendItemComparator(new Comparator() { + @Override + public int compare(XYLegendItem o1, XYLegendItem o2) { + if(o1.type == o2.type) { + return o1.getTitle().compareTo(o2.getTitle()); + } else { + return(o1.type.compareTo(o2.type)); + } + } + }); } protected void drawRegionLegendIcon(Canvas canvas, RectF rect, XYRegionFormatter formatter) { - canvas.drawRect(rect, formatter.getPaint()); - } - - private void drawRegionLegendCell(Canvas canvas, XYRegionFormatter formatter, RectF cellRect, String text) { - RectF iconRect = getIconRect(cellRect); - beginDrawingCell(canvas, iconRect); - - drawRegionLegendIcon( - canvas, - iconRect, - formatter - ); - finishDrawingCell(canvas, cellRect, iconRect, text); + canvas.drawRect(rect, formatter.getPaint()); } - private void drawSeriesLegendCell(Canvas canvas, XYSeriesRenderer renderer, XYSeriesFormatter formatter, RectF cellRect, String seriesTitle) { - RectF iconRect = getIconRect(cellRect); - beginDrawingCell(canvas, iconRect); - - renderer.drawSeriesLegendIcon( - canvas, - iconRect, - formatter); - finishDrawingCell(canvas, cellRect, iconRect, seriesTitle); + @Override + protected void drawIcon(@NonNull Canvas canvas, @NonNull RectF iconRect, @NonNull XYLegendItem XYLegendItem) { + switch (XYLegendItem.type) { + case REGION: + drawRegionLegendIcon(canvas, iconRect, (XYRegionFormatter) XYLegendItem.item); + break; + case SERIES: + final XYSeriesFormatter formatter = (XYSeriesFormatter) XYLegendItem.item; + plot.getRenderer(formatter.getRendererClass()).drawSeriesLegendIcon(canvas, iconRect, formatter); + break; + default: + throw new UnsupportedOperationException("Unexpected item type: " + XYLegendItem.type); + } } -// protected List> getLegendEnabledSeriesAndFormatterList() { -// List> sfList = new ArrayList<>(); -// ListIterator> it = plot.getSeriesRegistry().listIterator(); -// while(it.hasNext()) { -// SeriesAndFormatter thisSf = it.next(); -// if(thisSf.getFormatter().isLegendIconEnabled()) { -// sfList.add(thisSf); -// } -// } -// return sfList; -// } - @Override - protected synchronized void doOnDraw(Canvas canvas, RectF widgetRect) { - if(plot.isEmpty()) { - return; + protected List getLegendItems() { + final ArrayList items = new ArrayList<>(); + for (SeriesBundle sfPair : plot.getRegistry().getLegendEnabledItems()) { + items.add(new XYLegendItem(XYLegendItem.Type.SERIES, sfPair.getFormatter(), sfPair.getSeries().getTitle())); } - // Keep an alphabetically sorted list of regions: - TreeSet> sortedRegions = new TreeSet>(new RegionEntryComparator()); - - // Calculate the number of cells needed to draw the Legend: - int seriesCount = plot.getRegistry().size(); - - for(XYSeriesRenderer renderer : plot.getRendererList()) { + for (XYSeriesRenderer renderer : plot.getRendererList()) { Hashtable urf = renderer.getUniqueRegionFormatters(); - sortedRegions.addAll(urf.entrySet()); - } - - seriesCount += sortedRegions.size(); - - // Create an iterator specially created to draw the number of cells we calculated: - Iterator it = tableModel.getIterator(widgetRect, seriesCount); - - RectF cellRect; - - // draw each series legend item: - for(SeriesBundle sfPair : plot.getRegistry().getLegendEnabledItems()) { - //for(SeriesAndFormatter sfPair : plot.getSeriesRegistry()) { - cellRect = it.next(); - XYSeriesFormatter format = sfPair.getFormatter(); - drawSeriesLegendCell(canvas, plot.getRenderer(sfPair.getFormatter().getRendererClass()), - format, cellRect, sfPair.getSeries().getTitle()); - } - - // draw each region legend item: - for(Map.Entry entry : sortedRegions) { - if(!it.hasNext()) { - break; + for (Entry entry : urf.entrySet()) { + items.add(new XYLegendItem(XYLegendItem.Type.REGION, entry.getKey(), entry.getValue())); } - cellRect = it.next(); - XYRegionFormatter formatter = entry.getKey(); - drawRegionLegendCell(canvas, formatter, cellRect, entry.getValue()); } - } - - - public Paint getTextPaint() { - return textPaint; - } - - public void setTextPaint(Paint textPaint) { - this.textPaint = textPaint; - } - - public boolean isDrawIconBackgroundEnabled() { - return drawIconBackgroundEnabled; - } - - public void setDrawIconBackgroundEnabled(boolean drawIconBackgroundEnabled) { - this.drawIconBackgroundEnabled = drawIconBackgroundEnabled; - } - - public boolean isDrawIconBorderEnabled() { - return drawIconBorderEnabled; - } - - public void setDrawIconBorderEnabled(boolean drawIconBorderEnabled) { - this.drawIconBorderEnabled = drawIconBorderEnabled; - } - - public TableModel getTableModel() { - return tableModel; - } - - public Size getIconSize() { - return iconSize; - } - /** - * Set the size of each legend's icon. Note that when using relative sizing, - * the size is calculated against the countaining cell's size, not the plot's size. - * @param iconSize - */ - public void setIconSize(Size iconSize) { - this.iconSize = iconSize; + return items; } } diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java b/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java index 15e8bbc2..67eaafa8 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java @@ -48,9 +48,6 @@ */ public class XYPlot extends Plot { - private static final int DEFAULT_LEGEND_WIDGET_H_DP = 10; - private static final int DEFAULT_LEGEND_WIDGET_ICON_SIZE_DP = 7; - private static final int DEFAULT_GRAPH_WIDGET_H_DP = 18; private static final int DEFAULT_GRAPH_WIDGET_W_DP = 10; @@ -60,8 +57,11 @@ public class XYPlot extends Plot renderStack = new RenderStack<>(plot); + + renderStack.sync(); + assertEquals(2, renderStack.getElements().size()); + for(RenderStack.StackElement element : renderStack.getElements()) { + assertTrue(element.isEnabled()); + } + + renderStack.disable(LineAndPointRenderer.class); + assertEquals(2, renderStack.getElements().size()); + for(RenderStack.StackElement element : renderStack.getElements()) { + assertFalse(element.isEnabled()); + } + } +} diff --git a/androidplot-core/src/test/java/com/androidplot/xy/XYLegendWidgetTest.java b/androidplot-core/src/test/java/com/androidplot/xy/XYLegendWidgetTest.java index bbb2bec5..b50f7237 100644 --- a/androidplot-core/src/test/java/com/androidplot/xy/XYLegendWidgetTest.java +++ b/androidplot-core/src/test/java/com/androidplot/xy/XYLegendWidgetTest.java @@ -17,61 +17,101 @@ package com.androidplot.xy; import android.graphics.*; -import com.androidplot.Plot; import com.androidplot.test.AndroidplotTest; -import org.junit.After; +import com.androidplot.ui.DynamicTableModel; +import com.androidplot.ui.LayoutManager; +import com.androidplot.ui.Size; +import com.androidplot.ui.SizeMode; +import com.google.common.collect.Lists; + +import org.junit.Before; import org.junit.Test; -import org.robolectric.RuntimeEnvironment; -import java.util.Arrays; -import static junit.framework.Assert.assertEquals; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.Mockito; + +import java.util.List; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class XYLegendWidgetTest extends AndroidplotTest { - static class MockXYPlot extends XYPlot { + @Mock LayoutManager layoutManager; + @Mock XYPlot xyPlot; + @Mock Canvas canvas; + @Mock XYRegionFormatter xyRegionFormatter; + LineAndPointRenderer lineAndPointRenderer; - public MockXYPlot() { - super(RuntimeEnvironment.application, "Test", - Plot.RenderMode.USE_MAIN_THREAD); - } + Size widgetSize = new Size(100, SizeMode.ABSOLUTE, 100, SizeMode.ABSOLUTE); + Size iconSize = new Size(10, SizeMode.ABSOLUTE, 10, SizeMode.ABSOLUTE); + XYSeriesRegistry seriesRegistry; - public void exposedOnSizeChanged(int w, int h, int oldw, int oldh) { - this.onSizeChanged(w, h, oldw, oldh); - } + XYLegendWidget legendWidget; - public void exposedOnDraw(Canvas canvas) { - this.onDraw(canvas); - } - } + @Before + public void before() { + seriesRegistry = new XYSeriesRegistry(); + legendWidget = spy(new XYLegendWidget(layoutManager, xyPlot, widgetSize, + new DynamicTableModel(4, 4), iconSize)); - @After - public void tearDown() throws Exception {} + lineAndPointRenderer = new LineAndPointRenderer(xyPlot); - @Test - public void testDoOnDraw() throws Exception { - MockXYPlot plot = new MockXYPlot(); + when(xyPlot.getRegistry()).thenReturn(seriesRegistry); + when(xyPlot.getRendererList()).thenReturn(Lists.newArrayList(lineAndPointRenderer)); + when(xyPlot.getRenderer(any(Class.class))).thenReturn(lineAndPointRenderer); + } - SimpleXYSeries s1 = new SimpleXYSeries((Arrays.asList(1, 2, 3)), - SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "s1"); + @Test + public void draw_drawsLegendIcons_forEnabledItemsOnly() throws Exception { + final XYSeries s1 = mock(XYSeries.class); + final XYSeriesFormatter f1 = new LineAndPointFormatter(); + f1.setLegendIconEnabled(true); + + final XYSeries s2 = mock(XYSeries.class); + final XYSeriesFormatter f2 = new LineAndPointFormatter(); + f2.setLegendIconEnabled(false); + + final RectRegion r1 = new RectRegion(0, 0, 10, 10, "r1"); + final RectRegion r2 = new RectRegion(0, 0, 20, 20, "r2"); + f1.addRegion(r1, new XYRegionFormatter(0)); + f2.addRegion(r2, new XYRegionFormatter(0)); + + seriesRegistry.add(s1, f1); + seriesRegistry.add(s2, f2); + legendWidget.draw(canvas); + + verify(legendWidget, times(2)) + .drawRegionLegendIcon(any(Canvas.class), any(RectF.class), any(XYRegionFormatter.class)); + verify(legendWidget, times(3)) + .drawIcon(any(Canvas.class), any(RectF.class), any(XYLegendItem.class)); + } - plot.addSeries(s1, new LineAndPointFormatter( - Color.RED, Color.GREEN, Color.BLUE, null)); + @Test + public void draw_sortsItemsAlphabeticallyByTitle() throws Exception{ + final XYLegendItem i1 = new XYLegendItem(XYLegendItem.Type.SERIES, + new LineAndPointFormatter(), "zoo"); + final XYLegendItem i2 = new XYLegendItem(XYLegendItem.Type.SERIES, + new LineAndPointFormatter(), "apple"); + final XYLegendItem i3 = new XYLegendItem(XYLegendItem.Type.SERIES, + new LineAndPointFormatter(), "boo"); - assertEquals(1, plot.getRegistry().size()); + final List legendItems = Lists.newArrayList(i1, i2, i3); + doReturn(legendItems).when(legendWidget).getLegendItems(); - plot.exposedOnSizeChanged(100, 100, 100, 100); - plot.redraw(); - // have to manually invoke this because the invalidate() - // invoked by redraw() is a stub and will not result in onDraw being called. - plot.exposedOnDraw(new Canvas()); + legendWidget.draw(canvas); - plot.removeSeries(s1); - assertEquals(0, plot.getRegistry().size()); - plot.addSeries(s1, new BarFormatter(Color.RED, Color.GREEN)); - plot.redraw(); + InOrder inOrder = Mockito.inOrder(legendWidget); - // throws NullPointerException before fix - // for ANDROIDPLOT-166 was applied. - plot.exposedOnDraw(new Canvas()); + inOrder.verify(legendWidget).drawIcon(any(Canvas.class), any(RectF.class), eq(i2)); + inOrder.verify(legendWidget).drawIcon(any(Canvas.class), any(RectF.class), eq(i3)); + inOrder.verify(legendWidget).drawIcon(any(Canvas.class), any(RectF.class), eq(i1)); } - } diff --git a/demoapp/src/main/java/com/androidplot/demos/SimplePieChartActivity.java b/demoapp/src/main/java/com/androidplot/demos/SimplePieChartActivity.java index 03b5b12b..9484176f 100644 --- a/demoapp/src/main/java/com/androidplot/demos/SimplePieChartActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/SimplePieChartActivity.java @@ -62,6 +62,9 @@ public void onCreate(Bundle savedInstanceState) // initialize our XYPlot reference: pie = (PieChart) findViewById(R.id.mySimplePieChart); + // enable the legend: + pie.getLegend().setVisible(true); + final float padding = PixelUtils.dpToPix(30); pie.getPie().setPadding(padding, padding, padding, padding); @@ -183,36 +186,10 @@ protected void setupIntroAnimation() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { float scale = valueAnimator.getAnimatedFraction(); -// scalingSeries1.setScale(scale); -// scalingSeries2.setScale(scale); renderer.setExtentDegs(360 * scale); pie.redraw(); } }); -// animator.addListener(new Animator.AnimatorListener() { -// @Override -// public void onAnimationStart(Animator animator) { -// -// } -// -// @Override -// public void onAnimationEnd(Animator animator) { -// // the animation is over, so show point labels: -// series1Format.getPointLabelFormatter().getTextPaint().setColor(Color.WHITE); -// series2Format.getPointLabelFormatter().getTextPaint().setColor(Color.WHITE); -// plot.redraw(); -// } -// -// @Override -// public void onAnimationCancel(Animator animator) { -// -// } -// -// @Override -// public void onAnimationRepeat(Animator animator) { -// -// } -// }); // the animation will run for 1.5 seconds: animator.setDuration(1500); diff --git a/docs/legend.md b/docs/legend.md new file mode 100644 index 00000000..37c2d136 --- /dev/null +++ b/docs/legend.md @@ -0,0 +1,59 @@ +# The Legend +For `Plot` types that support it, the legend displays a list of elements in the plot along with +a color coded icon. The color coded icon is automatically generated using the colors and line styles +used to render the associated item. In the case of a `Series`, this is the `Formatter` you associated +with the `Series` when you added it to your `Plot`. + +# Showing / Hiding the Legend +Depending on the `Plot` type(s) you are using, the legend may or may not be visible by default. To +can enable / disable the legend: + +```java +plot.getLegend().setVisible(true|false); +``` + +# Hiding Series Items +You can tell Androidplot not to generate a legend item for a Series by configuring it's associated +`Formatter`: + +```java +formatter.setLegendIconEnabled(false); +``` + +## The TableModel +The `TableModel` controls how and where each item in the legend is drawn. Androidplot provides two +default implementations; `DynamicTableModel` and `FixedTableModel` (detailed below). All `TableModel` implementations +organize elements into a grid. This grid is populated with items based on the order which it's corresponding +series was added to the plot. This ordering can be further controlled by setting the `TableModel`'s +`TableOrder` param to either [ROW_MAJOR](https://en.wikipedia.org/wiki/Row-major_order) (items are added left-to-right, top-down) +or `COLUMN_MAJOR` (items are added top-down, left-to-right). + +### DynamicTableModel +The `DynamicTableModel` takes a desired of numbered rows and columns and evenly subdivides the `LegendWidget`'s +visible space into cells. For example, A 2x2 legend using `ROW_MAJOR` ordering: + +```java +plot.getLegend().setTableModel(new DynamicTableModel(2, 2, TableOrder.ROW_MAJOR)); +``` + +### FixedTableModel +The `FixedTableModel` takes a desired size of each cell in pixels and adds cells using the specified `TableOrder`. +It automatically wraps to the next row or column (based on `TableOrder`) when the cell being added +exceeds the legend's available space on a given axis. For example, A `FixedTableModel` using 300w*100h cells and +a TableOrder of `COLUMN_MAJOR`: + +```java +plot.getLegend().setTableModel(new FixedTableModel(PixelUtils.dpToPix(300), + PixelUtils.dpToPix(100), TableOrder.COLUMN_MAJOR)); +``` + +# Sorting Legend Entries +You can control the order of Legend entries by setting a custom `Comparator` on the legend: + +```java +Comparator<...> myComparator = ... +plot.getLegend().setLegendItemComparator(myComparator); +``` + +Using a custom `Comparator` in conjunction with `ROW_MAJOR` and `COLUMN_MAJOR` properties on the `TableModel` +(show above) gives you full control over the display ordering of your legend entries. \ No newline at end of file diff --git a/docs/release_notes.md b/docs/release_notes.md index d9a0ef4a..7ea22016 100644 --- a/docs/release_notes.md +++ b/docs/release_notes.md @@ -2,17 +2,32 @@ For details on what to expect in general when updating to a new version of Androiplot, check out the [versioning doc](versioning.md). +# 1.5.0 + +_Updates to legend functionality in this version may result in changes to the display order +of legend items in some cases. A custom `Comparator` can be used to resolve this if necessary; +see the [legend doc](legend.md) for implementation details._ + +* Added [legend doc](legend.md) +* Added legend support to `PieChart` +* Added configurable legend item sorting +* (#45) Auto range boundaries calculation fix for when using a fixed domain range and a `FastXYSeries` +* Minor Performance Optimizations + # 1.4.3 + * (#39) `FastLineAndPointRenderer` now renders vertices for legend items. * Added [XML Attrs reference doc](attrs.md). # 1.4.2 + * (#32) New step mode: `INCREMENT_BY_FIT`. -* (#33) PanZoom support for 'INCREMENT_BY_FIT'. +* (#33) `PanZoom` support for `INCREMENT_BY_FIT`. * (#34) Removed examples and documentation for serializing `SeriesRegistry` to preserve state. # 1.4.1 -* (#26) Fixed an NPE issue when drawing null values with a PointLabeler. + +* (#26) Fixed an NPE issue when drawing null values with a `PointLabeler`. * Fixed a broken link in Quickstart doc. # 1.4.0 @@ -92,6 +107,7 @@ See the [pie chart documentation](piechart.md) for usage details. * Removed InteractiveXYPlot as PanZoom makes it obsolete. # 1.0.0 + This is a factor of several core elements of the Androidplot lib. The general theme was to make class and method names more intuitive and to make xml styling more powerful. diff --git a/docs/xyplot.md b/docs/xyplot.md index 1e363d12..2dab08db 100644 --- a/docs/xyplot.md +++ b/docs/xyplot.md @@ -292,44 +292,9 @@ See the [candlestick documentation](candlestick.md) Smooth lines can be created by applying the [Catmull-Rom interpolator](http://androidplot.com/smooth-curves-and-androidplot/) to your series' Format. -# The Legend -By default, Androidplot will automatically produce a legend for your Plot. You however choose to hide the legend -or you can customize it to suit your needs. - -# Hiding Legend Items -As mentioned above, Androidplot automatically produces a legend for your Plot. This "auto legend" includes -items for each series added to the plot. If you wish to omit a series from the legend: - -```java -formatter.setLegendIconEnabled(false); -``` - -## The TableModel -The `TableModel` controls how and where each item in the legend is drawn. Androidplot provides two -default implementations; `DynamicTableModel` and `FixedTableModel` (detailed below). All `TableModel` implementations -organize elements into a grid. This grid is populated with items based on the order which it's corresponding -series was added to the plot. This ordering can be further controlled by setting the `TableModel`'s -`TableOrder` param to either [ROW_MAJOR](https://en.wikipedia.org/wiki/Row-major_order) (items are added left-to-right, top-down) -or `COLUMN_MAJOR` (items are added top-down, left-to-right). - -### DynamicTableModel -The `DynamicTableModel` takes a desired of numbered rows and columns and evenly subdivides the `LegendWidget`'s -visible space into cells. For example, A 2x2 legend using `ROW_MAJOR` ordering: - -```java -plot.getLegend().setTableModel(new DynamicTableModel(2, 2, TableOrder.ROW_MAJOR)); -``` - -### FixedTableModel -The `FixedTableModel` takes a desired size of each cell in pixels and adds cells using the specified `TableOrder`. -It automatically wraps to the next row or column (based on `TableOrder`) when the cell being added -exceeds the legend's available space on a given axis. For example, A `FixedTableModel` using 300w*100h cells and -a TableOrder of `COLUMN_MAJOR`: - -```java -plot.getLegend().setTableModel(new FixedTableModel(PixelUtils.dpToPix(300), - PixelUtils.dpToPix(100), TableOrder.COLUMN_MAJOR)); -``` +# The Legend +By default, Androidplot will automatically produce a legend for your `XYPlot`. See [the legend](legend.md) doc +for usage details. # Graph Rotation Androidplot provides the `Widget.setRotation(Widget.Rotation)` method for controlling the orientation diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0429c501..12d6c54e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip From e58d4dadd428ce81b580c3df4938014036529eb7 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Sun, 28 May 2017 08:57:26 -0500 Subject: [PATCH 26/81] Uprev to 1.5.0 --- build.gradle | 2 +- docs/quickstart.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index f6deec86..f7124dd4 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ ext { theCompileSdkVersion = 25 theTargetSdkVersion = 25 theMinSdkVersion = 5 - theVersionName = '1.4.4' + theVersionName = '1.5.0' theVersionCode = 0 } diff --git a/docs/quickstart.md b/docs/quickstart.md index 4c629126..76060cd9 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -14,7 +14,7 @@ To use the library in your gradle project add the following to your build.gradle ```groovy dependencies { - compile "com.androidplot:androidplot-core:1.4.3" + compile "com.androidplot:androidplot-core:1.5.0" } ``` From a124e850eb31bdfd5447223bbe008c7cebe8b056 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Wed, 12 Jul 2017 08:14:15 -0500 Subject: [PATCH 27/81] Documentation updates --- docs/index.md | 1 + docs/legend.md | 8 ++++++-- docs/plot_composition.md | 26 +++++++++++++------------- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/docs/index.md b/docs/index.md index c42b6b06..abb8a729 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,6 +15,7 @@ specific plot types explaining styling and other advanced topics. * [Quickstart](quickstart.md) :star: * [Quickstart (YouTube Video)](https://www.youtube.com/watch?v=wEFkzQY_wWI) :movie_camera: * [Plot Composition](plot_composition.md) +* [The Legend](legend.md) * [XY Plots](xyplot.md) * [Bar Charts](barchart.md) * [Candlestick Charts](candlestick.md) diff --git a/docs/legend.md b/docs/legend.md index 37c2d136..c3ea90e4 100644 --- a/docs/legend.md +++ b/docs/legend.md @@ -13,7 +13,7 @@ plot.getLegend().setVisible(true|false); ``` # Hiding Series Items -You can tell Androidplot not to generate a legend item for a Series by configuring it's associated +You can tell Androidplot not to generate a legend item for a `Series` by configuring it's associated `Formatter`: ```java @@ -56,4 +56,8 @@ plot.getLegend().setLegendItemComparator(myComparator); ``` Using a custom `Comparator` in conjunction with `ROW_MAJOR` and `COLUMN_MAJOR` properties on the `TableModel` -(show above) gives you full control over the display ordering of your legend entries. \ No newline at end of file +(show above) gives you full control over the display ordering of your legend entries. + +# Positioning and Resizing +The legend is just an implementation of a Widget and is positioned and resized in the same ways +that all Widget instances are positioned. See the [Plot Composition](plot_composition.md) doc for details. \ No newline at end of file diff --git a/docs/plot_composition.md b/docs/plot_composition.md index 20b80952..3252b9f5 100644 --- a/docs/plot_composition.md +++ b/docs/plot_composition.md @@ -1,26 +1,26 @@ # Plot Composition -All plots in Androidplot inherit from the abstract base class Plot which provides common behaviors -for all Plot implementations. +All plots in Androidplot inherit from the abstract base class `Plot` which provides common behaviors +for all `Plot` implementations. # Widgets -Plots are composed of one or more Widgets. A Widget is an abstraction of a visual +Plots are composed of one or more Widgets. A `Widget` is an abstraction of a visual component that may be positioned and scaled within the visible area of a Plot. For example, -an XY Plot is typically composed of these 5 Widgets: +an `XYPlot` is typically composed of these 5 `Widgets`: * Title * Graph * Domain Label * Range Label -* Legend +* [Legend](legend.md) -All Plot implementations will contain at least one default Widget providing the core -behavior encapsulated by that Plot. In addition to moving and scaling these Widgets, developers may -also extend them and replace the Plot's default instance with the derived implenentation in order to +All implementations of `Plot` will contain at least one default `Widget` providing the core +behavior encapsulated by that `Plot`. In addition to moving and scaling a `Widget`, developers may +also extend them and replace the `Plot` instance's default instance with the derived implementation in order to get custom behavior. # The LayoutManager -The LayoutManager provides the logic for visually positioning and scaling Widgets; all Plot implementations -contain an instance of LayoutManager that can be retrieved via `Plot.getLayoutManager()`. +The `LayoutManager` provides the logic for visually positioning and scaling Widgets; all `Plot` implementations +contain an instance of `LayoutManager` that can be retrieved via `Plot.getLayoutManager()`. ## Z-Indexing Z-indexing is a 2D drawing concept which associates each drawable entity with a value that determines @@ -28,12 +28,12 @@ which elements get drawn onto the screen first, producing the visual effect that on top of others. While Androidplot uses the term "z-index" it's implemented internally as a linked list to prevent the possibility -of duplicate index values and therefore ensuring that the drawing order of Widgets is always explicit. +of duplicate index values and therefore ensuring that `Widget` drawing order is always explicit. The [Layerable](../androidplot-core/src/main/java/com/androidplot/util/Layerable.java) interface -defines methods used for manipulating the z-index of a Widget. +defines methods used for manipulating the z-index of a `Widget`. ## Adding & Removing Widgets -New Widgets can be added either to the front or back of the z-index using these methods: +New `Widget` instances can be added either to the front or back of the z-index using these methods: * `LayoutManager.addToTop(Widget)` * `LayoutManager.addToBottom(Widget)` From e6ac20ac8952e907ffee4cc503b198131547c55c Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Wed, 12 Jul 2017 09:27:32 -0500 Subject: [PATCH 28/81] uprev to 1.5.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f7124dd4..d81cbf77 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ ext { theCompileSdkVersion = 25 theTargetSdkVersion = 25 theMinSdkVersion = 5 - theVersionName = '1.5.0' + theVersionName = '1.5.1' theVersionCode = 0 } From d448f5c047e3e9ccdba6b768a3ee12341d54d7ef Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Fri, 4 Aug 2017 07:51:18 -0500 Subject: [PATCH 29/81] #55 Fixes PieRenderer.getContainingSegment for segments larger than 50% of the pie. (#57) --- .../java/com/androidplot/pie/PieRenderer.java | 38 ++++++++++------- .../com/androidplot/pie/PieRendererTest.java | 41 ++++++++++++++++++- 2 files changed, 63 insertions(+), 16 deletions(-) diff --git a/androidplot-core/src/main/java/com/androidplot/pie/PieRenderer.java b/androidplot-core/src/main/java/com/androidplot/pie/PieRenderer.java index 8b663a82..dc83e620 100644 --- a/androidplot-core/src/main/java/com/androidplot/pie/PieRenderer.java +++ b/androidplot-core/src/main/java/com/androidplot/pie/PieRenderer.java @@ -30,11 +30,14 @@ */ public class PieRenderer extends SeriesRenderer { + private static final float FULL_PIE_DEGS = 360f; + private static final float HALF_PIE_DEGS = 180f; + // starting angle to use when drawing the first radial line of the first segment. private float startDegs = 0; // number of degrees to extend from startDegs; can be used to "shape" the pie chart. - private float extentDegs = 360; + private float extentDegs = FULL_PIE_DEGS; // TODO: express donut in units other than px. private float donutSize = 0.5f; @@ -240,7 +243,7 @@ protected PointF calculateLineEnd(float x, float y, float rad, float deg) { protected PointF calculateLineEnd(PointF origin, float rad, float deg) { - double radians = deg * Math.PI / 180F; + double radians = deg * Math.PI / HALF_PIE_DEGS; double x = rad * Math.cos(radians); double y = rad * Math.sin(radians); @@ -292,11 +295,10 @@ public Segment getContainingSegment(PointF point) { float dx = point.x - origin.x; float dy = point.y - origin.y; double theta = Math.atan2(dy, dx); - double angle = (theta * (180f / Math.PI)); + double angle = (theta * (HALF_PIE_DEGS / Math.PI)); if (angle < 0) { - // convert angle to 0-360 range with 0 being in the - // traditional "east" orientation: - angle += 360f; + // bring into 0-360 range + angle += FULL_PIE_DEGS; } // find the segment whose starting and ending angle (degs) contains @@ -310,10 +312,16 @@ public Segment getContainingSegment(PointF point) { float lastOffset = offset; float sweep = (float) (scale * (values[i]) * extentDegs); offset += sweep; - offset = offset % 360; + offset = offset % FULL_PIE_DEGS; final double dist = signedDistance(offset, angle); - if(dist > 0 && dist <= signedDistance(offset, lastOffset)) { + double endDist = signedDistance(offset, lastOffset); + if(endDist < 0) { + // segment accounts for more than 50% of the pie and wrapped around + // need to correct: + endDist = FULL_PIE_DEGS + endDist; + } + if(dist > 0 && dist <= endDist) { return sfPair.getSeries(); } i++; @@ -328,10 +336,10 @@ public Segment getContainingSegment(PointF point) { * @return */ protected static float degsToScreenDegs(float degs) { - degs = degs % 360; + degs = degs % FULL_PIE_DEGS; if (degs > 0) { - return 360 - degs; + return FULL_PIE_DEGS - degs; } else { return degs; } @@ -344,12 +352,12 @@ protected static float degsToScreenDegs(float degs) { * @return */ protected static double signedDistance(double angle1, double angle2) { - double d = Math.abs(angle1 - angle2) % 360; - double r = d > 180 ? 360 - d : d; + double d = Math.abs(angle1 - angle2) % FULL_PIE_DEGS; + double r = d > HALF_PIE_DEGS ? FULL_PIE_DEGS - d : d; //calculate sign - int sign = (angle1 - angle2 >= 0 && angle1 - angle2 <= 180) - || (angle1 - angle2 <= -180 && angle1 - angle2 >= -360) ? 1 : -1; + int sign = (angle1 - angle2 >= 0 && angle1 - angle2 <= HALF_PIE_DEGS) + || (angle1 - angle2 <= -HALF_PIE_DEGS && angle1 - angle2 >= -FULL_PIE_DEGS) ? 1 : -1; r *= sign; return r; } @@ -359,7 +367,7 @@ protected static double signedDistance(double angle1, double angle2) { * @param degs */ protected static void validateInputDegs(float degs) { - if(degs < 0 || degs > 360) { + if(degs < 0 || degs > FULL_PIE_DEGS) { throw new IllegalArgumentException("Degrees values must be between 0.0 and 360."); } } diff --git a/androidplot-core/src/test/java/com/androidplot/pie/PieRendererTest.java b/androidplot-core/src/test/java/com/androidplot/pie/PieRendererTest.java index dba4ce43..e504582a 100644 --- a/androidplot-core/src/test/java/com/androidplot/pie/PieRendererTest.java +++ b/androidplot-core/src/test/java/com/androidplot/pie/PieRendererTest.java @@ -110,7 +110,7 @@ public void testOnRender() throws Exception { } @Test - public void testGetContainingSegment() throws Exception { + public void getContainingSegment_returnsCorrectSegment() throws Exception { Segment segment1 = spy(new Segment("s1", 25)); Segment segment2 = spy(new Segment("s2", 25)); Segment segment3 = spy(new Segment("s3", 25)); @@ -150,6 +150,45 @@ public void testGetContainingSegment() throws Exception { assertEquals(segment1, renderer.getContainingSegment(new PointF(100, 0))); } + @Test + public void getContainingSegment_handlesSegmentsLargerThanHalfPie() throws Exception { + Segment segment1 = spy(new Segment("s1", 25)); + Segment segment2 = spy(new Segment("s2", 24)); + Segment segment3 = spy(new Segment("s3", 51)); + SegmentFormatter formatter = spy( + new SegmentFormatter(Color.GREEN, Color.GREEN, Color.GREEN, Color.GREEN)); + PieRenderer renderer = formatter.getRendererInstance(pieChart); + + pieChart.addSegment(segment1, formatter); + pieChart.addSegment(segment2, formatter); + pieChart.addSegment(segment3, formatter); + + // southeast + assertEquals(segment1, renderer.getContainingSegment(new PointF(100, 100))); + + // southwest + assertEquals(segment2, renderer.getContainingSegment(new PointF(0, 100))); + + // northwest + assertEquals(segment3, renderer.getContainingSegment(new PointF(0, 0))); + + // northeast + assertEquals(segment3, renderer.getContainingSegment(new PointF(100, 0))); + + renderer.setStartDegs(90); + // southeast + assertEquals(segment2, renderer.getContainingSegment(new PointF(100, 100))); + + // southwest + assertEquals(segment3, renderer.getContainingSegment(new PointF(0, 100))); + + // northwest + assertEquals(segment3, renderer.getContainingSegment(new PointF(0, 0))); + + // northeast + assertEquals(segment1, renderer.getContainingSegment(new PointF(100, 0))); + } + @Test public void testDegsToScreenDegs() throws Exception { assertEquals(0f, PieRenderer.degsToScreenDegs(0)); From 44c9b621cd6801b6fc368fea14932a2b50891162 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Sat, 5 Aug 2017 09:29:03 -0500 Subject: [PATCH 30/81] Updates buildscript to CircleCI 2.0 --- .circleci/config.yml | 99 +++++++++++++++++++++++++++++++++++ .gitignore | 3 +- androidplot-core/build.gradle | 4 +- build.gradle | 2 +- circle.yml | 59 --------------------- demoapp-wearable/build.gradle | 2 +- 6 files changed, 104 insertions(+), 65 deletions(-) create mode 100644 .circleci/config.yml delete mode 100644 circle.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..43c50cfc --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,99 @@ +# Java Gradle CircleCI 2.0 configuration file +# +# Check https://circleci.com/docs/2.0/language-java/ for more details +# +version: 2 + +general: + branches: + only: + #- circleci +jobs: + build: + docker: + # specify the version you desire here + #- image: circleci/openjdk:8-jdk + + # CircleCI maintains a library of pre-built images + # documented at https://circleci.com/docs/2.0/circleci-images/ + - image: circleci/android:api-25-alpha + + working_directory: ~/repo + + environment: + # Customize the JVM maximum heap limit + JVM_OPTS: -Xmx3200m + TERM: dumb +# KEYSTORE: ${CIRCLE_WORKING_DIRECTORY}/sigining.keystore +# PUBLISHER_ACCT_JSON_FILE: ${CIRCLE_WORKING_DIRECTORY}/publisher_profile.json + + steps: + - checkout + + - run: echo 'export KEYSTORE=${HOME}/repo/sigining.keystore' >> $BASH_ENV + - run: echo 'export PUBLISHER_ACCT_JSON_FILE=${HOME}/repo/publisher_profile.json' >> $BASH_ENV + + # Download and cache dependencies + - restore_cache: + keys: + - v1-dependencies-{{ checksum "build.gradle" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- + + # Get private keys etc + - run: ./misc/download_keystore.sh + - run: ./misc/inject_circle_build_number.sh + + - run: ./gradlew dependencies + + - save_cache: + paths: + - ~/.m2 + key: v1-dependencies-{{ checksum "build.gradle" }} + + # run tests & code coc! + - run: ./gradlew testDebug jacocoTestReportDebug + + # build release + - run: ./gradlew assembleRelease + + # javadoc + - run: ./gradlew javadoc + + # trigger codecod.io + - run: bash <(curl -s https://codecov.io/bash) + + - store_artifacts: + path: androidplot-core/build/outputs/aar/ + destination: aar + + - store_artifacts: + path: demoapp/build/outputs/apk/ + destination: apk + + - store_artifacts: + path: androidplot-core/build/reports/jacoco/debug/ + destination: coverage_report + + - store_artifacts: + path: androidplot-core/build/reports/tests/ + destination: test_results + + - store_test_results: + path: androidplot-core/build/test-results/ + + - deploy: + name: "Deploy to Bintray" + command: | + if [ "${CIRCLE_BRANCH}" == "master" ]; + then ./gradlew bintrayUpload; + fi + + - deploy: + name: "Deploy to Google Play" + command: | + if [ "${CIRCLE_BRANCH}" == "master" ]; + then + ./misc/download_google_publisher_json.sh; + ./gradlew publishApkRelease + fi \ No newline at end of file diff --git a/.gitignore b/.gitignore index 58204be4..694f2995 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,5 @@ DemoApp/.settings DemoApp/bin DemoApp/gen DemoApp/target -.idea/libraries -.idea/*.xml +.idea **/R.java diff --git a/androidplot-core/build.gradle b/androidplot-core/build.gradle index aff191ce..3e305c7d 100644 --- a/androidplot-core/build.gradle +++ b/androidplot-core/build.gradle @@ -74,7 +74,7 @@ android { lintOptions { abortOnError false } - buildToolsVersion '25.0.0' + buildToolsVersion theBuildToolsVersion } group = 'com.androidplot' @@ -86,7 +86,7 @@ def gitUrl = 'https://github.com/halfhp/androidplot.git' dependencies { compile 'com.halfhp.fig:figlib:1.0.3' - compile 'com.android.support:support-annotations:24.2.0' + compile 'com.android.support:support-annotations:25.3.1' testCompile "org.mockito:mockito-core:1.10.19" testCompile group: 'junit', name: 'junit', version: '4.12' testCompile "org.robolectric:robolectric:3.1" diff --git a/build.gradle b/build.gradle index d81cbf77..7b379558 100644 --- a/build.gradle +++ b/build.gradle @@ -38,7 +38,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.3.2' + classpath 'com.android.tools.build:gradle:2.3.3' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7' classpath 'com.vanniktech:gradle-android-junit-jacoco-plugin:0.5.0' diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 494b4bc2..00000000 --- a/circle.yml +++ /dev/null @@ -1,59 +0,0 @@ - -machine: - environment: - KEYSTORE: ${HOME}/${CIRCLE_PROJECT_REPONAME}/sigining.keystore - PUBLISHER_ACCT_JSON_FILE: ${HOME}/${CIRCLE_PROJECT_REPONAME}/publisher_profile.json - -dependencies: - - pre: - - if [ ! -e /usr/local/android-sdk-linux/platforms/android-25 ]; then echo y | android update sdk --all --no-ui --filter "android-25"; fi; - - if [ ! -e /usr/local/android-sdk-linux/build-tools/25.0.2 ]; then echo y | android update sdk --all --no-ui --filter "build-tools-25.0.2"; fi; - - bash ./misc/download_keystore.sh - - bash ./misc/inject_circle_build_number.sh - -test: - - override: - - (./gradlew test assembleRelease javadoc): - timeout: 360 - - post: - - # core lib: - - cp -r ${HOME}/${CIRCLE_PROJECT_REPONAME}/androidplot-core/build/outputs/aar/ $CIRCLE_ARTIFACTS - - # demo app .apk: - - cp -r ${HOME}/${CIRCLE_PROJECT_REPONAME}/demoapp/build/outputs/apk/ $CIRCLE_ARTIFACTS - - # javadoc: - - cp -r ${HOME}/${CIRCLE_PROJECT_REPONAME}/androidplot-core/build/docs/javadoc/ $CIRCLE_ARTIFACTS - - - # junit xml report: - - mkdir -p $CIRCLE_TEST_REPORTS/junit-xml/ - - find . -type f -regex ".*/build/test-results/testReleaseUnitTest/.*xml" -exec cp {} $CIRCLE_TEST_REPORTS/junit-xml/ \; - - # junit html report: - # TODO: recursively copy subdirs etc - - mkdir -p $CIRCLE_TEST_REPORTS/junit-html/ - - cp -r ${HOME}/${CIRCLE_PROJECT_REPONAME}/androidplot-core/build/reports/tests/testReleaseUnitTest/* $CIRCLE_TEST_REPORTS/junit-html/ - - # lint report: - - mkdir -p $CIRCLE_TEST_REPORTS/lint/ - - find . -type f -regex ".*/build/outputs/.*html" -exec cp {} $CIRCLE_TEST_REPORTS/lint/ \; - - # code coverage: - - ./gradlew jacocoTestReportDebug - - mkdir -p $CIRCLE_TEST_REPORTS/jacoco/ - - cp -r ${HOME}/${CIRCLE_PROJECT_REPONAME}/androidplot-core/build/reports/jacoco/debug/. $CIRCLE_TEST_REPORTS/jacoco - - bash <(curl -s https://codecov.io/bash) - -deployment: - master: - branch: master - commands: - - (./gradlew bintrayUpload): - timeout: 360 - - bash ./misc/download_google_publisher_json.sh - - ./gradlew publishApkRelease diff --git a/demoapp-wearable/build.gradle b/demoapp-wearable/build.gradle index 37406aaa..8154801c 100644 --- a/demoapp-wearable/build.gradle +++ b/demoapp-wearable/build.gradle @@ -19,7 +19,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.2' + classpath 'com.android.tools.build:gradle:2.3.3' } } apply plugin: 'com.android.application' From a16a1df75adf417e4c18083e0f756eb746d6da36 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Sat, 5 Aug 2017 09:58:01 -0500 Subject: [PATCH 31/81] #52 - Added NPE check to Plot.renderOnCanvas (#59) --- androidplot-core/src/main/java/com/androidplot/Plot.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/androidplot-core/src/main/java/com/androidplot/Plot.java b/androidplot-core/src/main/java/com/androidplot/Plot.java index f2eab84a..3ee10174 100644 --- a/androidplot-core/src/main/java/com/androidplot/Plot.java +++ b/androidplot-core/src/main/java/com/androidplot/Plot.java @@ -21,6 +21,7 @@ import android.graphics.*; import android.os.Build; import android.os.Looper; +import android.support.annotation.Nullable; import android.util.AttributeSet; import android.util.Log; import android.view.View; @@ -804,7 +805,10 @@ protected void onDraw(Canvas canvas) { * "heavy lifting". * @param canvas */ - protected synchronized void renderOnCanvas(Canvas canvas) { + protected synchronized void renderOnCanvas(@Nullable Canvas canvas) { + if(canvas == null) { + return; + } try { // any series interested in synchronizing with plot should // implement PlotListener.onBeforeDraw(...) and do a read lock from within its From 9088a7b1ae536fceb9b570ec07c2eb39fa0a13a0 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Sat, 5 Aug 2017 09:59:50 -0500 Subject: [PATCH 32/81] updates quickstart lib version to 1.5.1 --- docs/quickstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quickstart.md b/docs/quickstart.md index 76060cd9..cf238b57 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -14,7 +14,7 @@ To use the library in your gradle project add the following to your build.gradle ```groovy dependencies { - compile "com.androidplot:androidplot-core:1.5.0" + compile "com.androidplot:androidplot-core:1.5.1" } ``` From 0cb45aef633bc64fcaf4eca17c5b1ffd454f350f Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Sat, 5 Aug 2017 21:01:15 -0500 Subject: [PATCH 33/81] uprev to 1.5.2 for development --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7b379558..dad2f2f7 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ ext { theCompileSdkVersion = 25 theTargetSdkVersion = 25 theMinSdkVersion = 5 - theVersionName = '1.5.1' + theVersionName = '1.5.2' theVersionCode = 0 } From 4662832d5c73b7bceba53c9dd948f8e9ff1f9026 Mon Sep 17 00:00:00 2001 From: guycnicholas Date: Wed, 8 Nov 2017 09:40:24 -0800 Subject: [PATCH 34/81] For issue #61 updated screenToSeriesY to use the vertical bounds rather than horizontal (#62) --- .../com/androidplot/xy/XYGraphWidget.java | 2 +- .../com/androidplot/xy/XYGraphWidgetTest.java | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) 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 a6460799..d1ce96ec 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java @@ -489,7 +489,7 @@ protected float seriesToScreenX(Number x) { protected float seriesToScreenY(Number y) { return (float) plot.getBounds().getyRegion(). - transform(y.doubleValue(), gridRect.left, gridRect.right, true); + transform(y.doubleValue(), gridRect.bottom, gridRect.top, true); } @Override diff --git a/androidplot-core/src/test/java/com/androidplot/xy/XYGraphWidgetTest.java b/androidplot-core/src/test/java/com/androidplot/xy/XYGraphWidgetTest.java index 4b9f156a..0bfe4d9d 100644 --- a/androidplot-core/src/test/java/com/androidplot/xy/XYGraphWidgetTest.java +++ b/androidplot-core/src/test/java/com/androidplot/xy/XYGraphWidgetTest.java @@ -79,7 +79,7 @@ public void setUp() throws Exception { xyPlot.setRangeStep(StepMode.INCREMENT_BY_VAL, 1); graphWidget = spy(new XYGraphWidget(layoutManager, xyPlot, size)); - graphWidget.setGridRect(new RectF(0, 0, 100, 100)); + graphWidget.setGridRect(new RectF(0, 0, 10, 100)); graphWidget.setLabelRect(new RectF(0, 0, 100, 100)); } @@ -253,11 +253,11 @@ public void testScreenToSeries() throws Exception { assertEquals(-100, coords.x.intValue()); assertEquals(100, coords.y.intValue()); - coords = graphWidget.screenToSeries(new PointF(100, 100)); + coords = graphWidget.screenToSeries(new PointF(10, 100)); assertEquals(100, coords.x.intValue()); assertEquals(-100, coords.y.intValue()); - coords = graphWidget.screenToSeries(new PointF(50, 50)); + coords = graphWidget.screenToSeries(new PointF(5, 50)); assertEquals(0, coords.x.intValue()); assertEquals(0, coords.y.intValue()); } @@ -271,11 +271,11 @@ public void testSeriesToScreen() throws Exception { assertEquals(0f, point.y); point = graphWidget.seriesToScreen(new XYCoords(100, -100)); - assertEquals(100f, point.x); + assertEquals(10f, point.x); assertEquals(100f, point.y); point = graphWidget.seriesToScreen(new XYCoords(0, 0)); - assertEquals(50f, point.x); + assertEquals(5f, point.x); assertEquals(50f, point.y); } @@ -284,8 +284,8 @@ public void testScreenToSeriesX() throws Exception { when(xyPlot.getBounds()).thenReturn(new RectRegion(-100, 100, -100, 100)); assertEquals(-100, graphWidget.screenToSeriesX(new PointF(0, 0)).intValue()); - assertEquals(100, graphWidget.screenToSeriesX(new PointF(100, 100)).intValue()); - assertEquals(0, graphWidget.screenToSeriesX(new PointF(50, 50)).intValue()); + assertEquals(100, graphWidget.screenToSeriesX(new PointF(10, 100)).intValue()); + assertEquals(0, graphWidget.screenToSeriesX(new PointF(5, 50)).intValue()); } @Test @@ -302,16 +302,16 @@ public void testSeriesToScreenX() throws Exception { when(xyPlot.getBounds()).thenReturn(new RectRegion(-100, 100, -100, 100)); assertEquals(0f, graphWidget.seriesToScreenX(-100)); - assertEquals(100f, graphWidget.seriesToScreenX(100)); - assertEquals(50f, graphWidget.seriesToScreenX(0)); + assertEquals(10f, graphWidget.seriesToScreenX(100)); + assertEquals(5f, graphWidget.seriesToScreenX(0)); } @Test public void testSeriesToScreenY() throws Exception { when(xyPlot.getBounds()).thenReturn(new RectRegion(-100, 100, -100, 100)); - assertEquals(0f, graphWidget.seriesToScreenY(100)); - assertEquals(100f, graphWidget.seriesToScreenY(-100)); + assertEquals(100f, graphWidget.seriesToScreenY(100)); + assertEquals(0f, graphWidget.seriesToScreenY(-100)); assertEquals(50f, graphWidget.seriesToScreenY(0)); } } From 55fce04bff334e0988aa253b25eeb667456d5be8 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Fri, 8 Dec 2017 08:43:17 -0600 Subject: [PATCH 35/81] Androidplot 1.5.2 (#65) * target Android SDK 26 * update fig dependency for gradle 3.x.x support * adds custom renderer documentation * remove obsolete class / unnecessary casts * adds sizing section to plot_composition.md * adds more sizing and positioning documentation --- .circleci/config.yml | 2 +- androidplot-core/build.gradle | 6 +- .../src/main/java/com/androidplot/Plot.java | 8 +- .../java/com/androidplot/ui/Formatter.java | 6 +- .../java/com/androidplot/ui/SizeMetric.java | 1 - .../ui/widget/LegendItemOrganizer.java | 9 -- .../java/com/androidplot/xy/BarRenderer.java | 2 +- .../com/androidplot/xy/XYRegionFormatter.java | 6 +- .../src/main/res/values/attrs.xml | 51 ++++++- build.gradle | 13 +- demoapp-wearable/build.gradle | 2 +- demoapp/build.gradle | 11 +- .../demos/SimpleXYPlotActivity.java | 6 +- .../demos/TouchZoomExampleActivity.java | 8 +- .../demos/XYRegionExampleActivity.java | 12 +- .../src/main/res/layout/bar_plot_example.xml | 54 ++++---- .../src/main/res/layout/demo_app_widget.xml | 17 ++- .../res/layout/dynamic_xyplot_example.xml | 22 +-- demoapp/src/main/res/layout/main.xml | 54 +++++--- demoapp/src/main/res/layout/pie_chart.xml | 3 +- .../main/res/layout/step_chart_example.xml | 25 ++-- .../main/res/layout/time_series_example.xml | 42 +++--- .../main/res/layout/touch_zoom_example.xml | 49 +++---- demoapp/src/main/res/values-hdpi/dimens.xml | 18 --- demoapp/src/main/res/values-ldpi/dimens.xml | 20 --- demoapp/src/main/res/values/dimens.xml | 18 --- demoapp/src/main/res/values/style.xml | 28 ---- docs/attrs.md | 50 ++++++- docs/custom_renderer.md | 101 ++++++++++++++ docs/grouprenderer.md | 8 +- docs/images/rounded_bar_renderer.png | Bin 0 -> 70585 bytes docs/images/sizing/abs100x-abs100y.png | Bin 0 -> 5852 bytes docs/images/sizing/abs100x-abs150y.png | Bin 0 -> 6725 bytes docs/images/sizing/abs100x-rel1y.png | Bin 0 -> 6049 bytes docs/images/sizing/fil50x-fil50y.png | Bin 0 -> 7882 bytes docs/images/sizing/rel075x-abs100y.png | Bin 0 -> 5907 bytes docs/index.md | 1 + docs/plot_composition.md | 131 +++++++++++++++++- docs/quickstart.md | 2 +- docs/release_notes.md | 12 ++ gradle/wrapper/gradle-wrapper.properties | 4 +- 41 files changed, 527 insertions(+), 275 deletions(-) delete mode 100644 androidplot-core/src/main/java/com/androidplot/ui/widget/LegendItemOrganizer.java delete mode 100644 demoapp/src/main/res/values-ldpi/dimens.xml delete mode 100644 demoapp/src/main/res/values/style.xml create mode 100644 docs/custom_renderer.md create mode 100644 docs/images/rounded_bar_renderer.png create mode 100644 docs/images/sizing/abs100x-abs100y.png create mode 100644 docs/images/sizing/abs100x-abs150y.png create mode 100644 docs/images/sizing/abs100x-rel1y.png create mode 100644 docs/images/sizing/fil50x-fil50y.png create mode 100644 docs/images/sizing/rel075x-abs100y.png diff --git a/.circleci/config.yml b/.circleci/config.yml index 43c50cfc..6a24c3f8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,7 +16,7 @@ jobs: # CircleCI maintains a library of pre-built images # documented at https://circleci.com/docs/2.0/circleci-images/ - - image: circleci/android:api-25-alpha + - image: circleci/android:api-26-alpha working_directory: ~/repo diff --git a/androidplot-core/build.gradle b/androidplot-core/build.gradle index 3e305c7d..a814a4c1 100644 --- a/androidplot-core/build.gradle +++ b/androidplot-core/build.gradle @@ -58,7 +58,6 @@ task generateAttrsMarkdown(type: AttrMarkdown) { android { compileSdkVersion theCompileSdkVersion - buildToolsVersion theBuildToolsVersion defaultConfig { versionCode theVersionCode @@ -74,7 +73,6 @@ android { lintOptions { abortOnError false } - buildToolsVersion theBuildToolsVersion } group = 'com.androidplot' @@ -85,8 +83,8 @@ def gitUrl = 'https://github.com/halfhp/androidplot.git' dependencies { - compile 'com.halfhp.fig:figlib:1.0.3' - compile 'com.android.support:support-annotations:25.3.1' + compile 'com.halfhp.fig:figlib:1.0.7' + compile 'com.android.support:support-annotations:27.0.2' testCompile "org.mockito:mockito-core:1.10.19" testCompile group: 'junit', name: 'junit', version: '4.12' testCompile "org.robolectric:robolectric:3.1" diff --git a/androidplot-core/src/main/java/com/androidplot/Plot.java b/androidplot-core/src/main/java/com/androidplot/Plot.java index 3ee10174..8b436199 100644 --- a/androidplot-core/src/main/java/com/androidplot/Plot.java +++ b/androidplot-core/src/main/java/com/androidplot/Plot.java @@ -520,7 +520,7 @@ private void loadAttrs(AttributeSet attrs, int defStyle) { // apply "configurator" attrs: (overrides any previously applied styleable attrs) // filter out androidplot prefixed attrs: - HashMap attrHash = new HashMap(); + HashMap attrHash = new HashMap<>(); for (int i = 0; i < attrs.getAttributeCount(); i++) { String attrName = attrs.getAttributeName(i); @@ -529,7 +529,11 @@ private void loadAttrs(AttributeSet attrs, int defStyle) { attrHash.put(attrName.substring(XML_ATTR_PREFIX.length() + 1), attrs.getAttributeValue(i)); } } - Fig.configure(getContext(), this, attrHash); + try { + Fig.configure(getContext(), this, attrHash); + } catch (FigException e) { + throw new RuntimeException(e); + } } } diff --git a/androidplot-core/src/main/java/com/androidplot/ui/Formatter.java b/androidplot-core/src/main/java/com/androidplot/ui/Formatter.java index 8f2a354b..d920ab36 100644 --- a/androidplot-core/src/main/java/com/androidplot/ui/Formatter.java +++ b/androidplot-core/src/main/java/com/androidplot/ui/Formatter.java @@ -48,7 +48,11 @@ public Formatter(Context ctx, int xmlCfgId) { } public void configure(Context ctx, int xmlCfgId) { - Fig.configure(ctx, this, xmlCfgId); + try { + Fig.configure(ctx, this, xmlCfgId); + } catch (FigException e) { + throw new RuntimeException(e); + } } /** diff --git a/androidplot-core/src/main/java/com/androidplot/ui/SizeMetric.java b/androidplot-core/src/main/java/com/androidplot/ui/SizeMetric.java index e3819acd..d7f3e58b 100644 --- a/androidplot-core/src/main/java/com/androidplot/ui/SizeMetric.java +++ b/androidplot-core/src/main/java/com/androidplot/ui/SizeMetric.java @@ -43,7 +43,6 @@ protected void validatePair(float value, SizeMode layoutType) { @Override public float getPixelValue(float size) { - //switch(layoutType) switch(getLayoutType()) { case ABSOLUTE: return getValue(); diff --git a/androidplot-core/src/main/java/com/androidplot/ui/widget/LegendItemOrganizer.java b/androidplot-core/src/main/java/com/androidplot/ui/widget/LegendItemOrganizer.java deleted file mode 100644 index 536ee9d8..00000000 --- a/androidplot-core/src/main/java/com/androidplot/ui/widget/LegendItemOrganizer.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.androidplot.ui.widget; - -import java.util.List; - - -public interface LegendItemOrganizer { - - void organize(List items); -} diff --git a/androidplot-core/src/main/java/com/androidplot/xy/BarRenderer.java b/androidplot-core/src/main/java/com/androidplot/xy/BarRenderer.java index c696bbf2..bb8c4c7f 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/BarRenderer.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/BarRenderer.java @@ -238,7 +238,7 @@ protected RectF createBarRect(float w1, float h1, float w2, float h2, BarFormatt return result; } - protected void drawBar(Canvas canvas, Bar bar, RectF rect) { + protected void drawBar(Canvas canvas, Bar bar, RectF rect) { // null yVals are skipped: if(bar.getY() == null) { diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYRegionFormatter.java b/androidplot-core/src/main/java/com/androidplot/xy/XYRegionFormatter.java index b0a3186f..0492c6c2 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYRegionFormatter.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYRegionFormatter.java @@ -43,7 +43,11 @@ public class XYRegionFormatter { public XYRegionFormatter(Context ctx, int xmlCfgId) { // prevent configuration of classes derived from this one: if (getClass().equals(XYRegionFormatter.class)) { - Fig.configure(ctx, this, xmlCfgId); + try { + Fig.configure(ctx, this, xmlCfgId); + } catch (FigException e) { + throw new RuntimeException(e); + } } } diff --git a/androidplot-core/src/main/res/values/attrs.xml b/androidplot-core/src/main/res/values/attrs.xml index e640706e..c9dc65a0 100644 --- a/androidplot-core/src/main/res/values/attrs.xml +++ b/androidplot-core/src/main/res/values/attrs.xml @@ -16,12 +16,17 @@ --> + @@ -442,6 +447,10 @@ __dimension|float|integer__ * relative_from_left * relative_from_right * relative_from_center + +`HorizontalPositioning` component of the `HorizontalPosition` of the `TextLabelWidget` +that displays the domain title. +See [Positioning Widgets](plot_composition.md#positioning-widgets) documentation. --> + xmlns:ap="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + ap:title="Growth" /> + android:layout_height="wrap_content" /> + android:layout_height="wrap_content" /> + android:layout_height="wrap_content" /> + android:progress="10" /> + android:progress="1" /> + android:checked="true" + android:text="Series 1" /> + android:checked="true" + android:text="Series 2" /> \ No newline at end of file diff --git a/demoapp/src/main/res/layout/demo_app_widget.xml b/demoapp/src/main/res/layout/demo_app_widget.xml index 85b5fe6b..81977453 100644 --- a/demoapp/src/main/res/layout/demo_app_widget.xml +++ b/demoapp/src/main/res/layout/demo_app_widget.xml @@ -17,14 +17,13 @@ --> + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> - - + \ No newline at end of file diff --git a/demoapp/src/main/res/layout/dynamic_xyplot_example.xml b/demoapp/src/main/res/layout/dynamic_xyplot_example.xml index ddfd0cba..5b0c5644 100644 --- a/demoapp/src/main/res/layout/dynamic_xyplot_example.xml +++ b/demoapp/src/main/res/layout/dynamic_xyplot_example.xml @@ -1,5 +1,4 @@ - - + xmlns:ap="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent"> + ap:legendTextSize="15sp" + ap:rangeTitle="Range" + ap:title="A Dynamic XY Plot" /> diff --git a/demoapp/src/main/res/layout/main.xml b/demoapp/src/main/res/layout/main.xml index 65a43ef9..590087de 100644 --- a/demoapp/src/main/res/layout/main.xml +++ b/demoapp/src/main/res/layout/main.xml @@ -32,99 +32,117 @@