From 18d64c8772599d6159d2a5eb1c27f55c9efd9b08 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Sat, 30 Apr 2016 08:33:13 -0500 Subject: [PATCH 001/133] * increased min supported sdk to 5. * CandlestickSeries experimental implementation --- .../src/main/java/com/androidplot/Bounds.java | 77 ++++++ .../main/java/com/androidplot/LineRegion.java | 27 +- .../src/main/java/com/androidplot/Plot.java | 11 +- .../java/com/androidplot/SeriesRegistry.java | 68 +++++ .../candlestick/CandlestickFormatter.java | 130 ++++++++++ .../candlestick/CandlestickRenderer.java | 115 +++++++++ .../CandlestickSeries.java} | 65 +++-- .../candlestick/SimpleCandlestickSeries.java | 117 +++++++++ .../java/com/androidplot/ui/RenderStack.java | 2 +- .../com/androidplot/ui/SeriesRenderer.java | 2 +- .../com/androidplot/util/SeriesUtils.java | 154 +++++++++++- .../java/com/androidplot/xy/BarRenderer.java | 2 +- .../java/com/androidplot/xy/BoundaryMode.java | 2 +- .../androidplot/xy/LineAndPointRenderer.java | 2 +- .../java/com/androidplot/xy/RectRegion.java | 2 - .../java/com/androidplot/xy/XYBounds.java | 97 ++++++++ .../com/androidplot/xy/XYConstraints.java | 155 ++++++++++++ .../com/androidplot/xy/XYLegendWidget.java | 2 +- .../main/java/com/androidplot/xy/XYPlot.java | 235 +++++------------- .../com/androidplot/xy/XYRegionFormatter.java | 5 - .../java/com/androidplot/xy/XYSeries.java | 15 +- .../com/androidplot/xy/XYSeriesRenderer.java | 8 +- .../com/androidplot/util/SeriesUtilsTest.java | 130 ++++++++++ .../java/com/androidplot/xy/XYPlotTest.java | 6 +- build.gradle | 2 +- demoapp/src/main/AndroidManifest.xml | 6 + .../demos/BarPlotExampleActivity.java | 8 +- .../demos/CandlestickChartActivity.java | 74 ++++++ .../com/androidplot/demos/MainActivity.java | 8 + .../demos/SimpleXYPlotActivity.java | 5 + demoapp/src/main/res/layout/main.xml | 161 +++++++----- 31 files changed, 1372 insertions(+), 321 deletions(-) create mode 100644 androidplot-core/src/main/java/com/androidplot/Bounds.java create mode 100644 androidplot-core/src/main/java/com/androidplot/SeriesRegistry.java create mode 100644 androidplot-core/src/main/java/com/androidplot/candlestick/CandlestickFormatter.java create mode 100644 androidplot-core/src/main/java/com/androidplot/candlestick/CandlestickRenderer.java rename androidplot-core/src/main/java/com/androidplot/{xy/XYGraphBounds.java => candlestick/CandlestickSeries.java} (55%) create mode 100644 androidplot-core/src/main/java/com/androidplot/candlestick/SimpleCandlestickSeries.java create mode 100644 androidplot-core/src/main/java/com/androidplot/xy/XYBounds.java create mode 100644 androidplot-core/src/main/java/com/androidplot/xy/XYConstraints.java create mode 100644 androidplot-core/src/test/java/com/androidplot/util/SeriesUtilsTest.java create mode 100644 demoapp/src/main/java/com/androidplot/demos/CandlestickChartActivity.java diff --git a/androidplot-core/src/main/java/com/androidplot/Bounds.java b/androidplot-core/src/main/java/com/androidplot/Bounds.java new file mode 100644 index 00000000..285e7dc9 --- /dev/null +++ b/androidplot-core/src/main/java/com/androidplot/Bounds.java @@ -0,0 +1,77 @@ +/* + * Copyright 2016 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; + +/** + * Defines simple min/max boundary. Differs from {@link LineRegion} in that it accepts null values. + */ +public class Bounds { + + private Number min; + private Number max; + + public Bounds() { + this(null, null); + } + + public Bounds(Number min, Number max) { + this.min = min; + this.max = max; + } + + public Number getMin() { + return min; + } + + public void setMin(Number min) { + this.min = min; + } + + public Number getMax() { + return max; + } + + public void setMax(Number max) { + this.max = max; + } + + /** + * Compares the input bounds min/max against this instance's current min/max. + * If the input.min is less than this.min then this.min will be set to input.min. + * If the input.max is greater than this.max then this.max will be set to input.max + * @param input + */ + public void union(Bounds input) { + if(this.min == null || input.min != null && + input.min.doubleValue() < this.min.doubleValue()) { + this.min = input.min; + } + if(this.max == null || input.max != null && input.max.doubleValue() > + this.max.doubleValue()) { + this.max = input.max; + } + } + + /** + * Inverse of {@link #union(Bounds)}. + * @param input + */ + public void intersect(Bounds input) { + // TODO + throw new UnsupportedOperationException("Not yet implemented."); + } +} diff --git a/androidplot-core/src/main/java/com/androidplot/LineRegion.java b/androidplot-core/src/main/java/com/androidplot/LineRegion.java index 69993855..22a7e1b7 100644 --- a/androidplot-core/src/main/java/com/androidplot/LineRegion.java +++ b/androidplot-core/src/main/java/com/androidplot/LineRegion.java @@ -24,22 +24,28 @@ public class LineRegion { private Number minVal; private Number maxVal; - public LineRegion(Number val1, Number v2) { - if (val1.doubleValue() < v2.doubleValue()) { - this.setMinVal(val1); + public LineRegion(Number v1, Number v2) { + if (v1 != null && v2 != null && v1.doubleValue() < v2.doubleValue()) { + this.setMinVal(v1); this.setMaxVal(v2); } else { this.setMinVal(v2); - this.setMaxVal(val1); + this.setMaxVal(v1); } } - public static Number measure(Number val1, Number val2) { - return new LineRegion(val1, val2).length(); + /** + * + * @param v1 + * @param v2 + * @return The distance between val1 and val2 or null if either parameters are null. + */ + public static Number measure(Number v1, Number v2) { + return new LineRegion(v1, v2).length(); } public Number length() { - return maxVal.doubleValue() - minVal.doubleValue(); + return maxVal == null || minVal == null ? null : maxVal.doubleValue() - minVal.doubleValue(); } /** @@ -63,13 +69,6 @@ public boolean intersects(LineRegion lineRegion) { */ public boolean intersects(Number line2Min, Number line2Max) { - //double l1min = getMinVal() == null ? Double.NEGATIVE_INFINITY : getMinVal().doubleValue(); - //double l1max = getMaxVal() == null ? Double.POSITIVE_INFINITY : getMaxVal().doubleValue(); - - //double l2min = line2Min == null ? Double.NEGATIVE_INFINITY : line2Min.doubleValue(); - //double l2max = line2Max == null ? Double.POSITIVE_INFINITY : line2Max.doubleValue(); - - // is this line completely within line2? if(line2Min.doubleValue() <= this.minVal.doubleValue() && line2Max.doubleValue() >= this.maxVal.doubleValue()) { return true; diff --git a/androidplot-core/src/main/java/com/androidplot/Plot.java b/androidplot-core/src/main/java/com/androidplot/Plot.java index 02e93078..8f01c8f3 100644 --- a/androidplot-core/src/main/java/com/androidplot/Plot.java +++ b/androidplot-core/src/main/java/com/androidplot/Plot.java @@ -66,7 +66,7 @@ public HashMap, RendererType> getRenderers() { /** * Associates lists series and getFormatter pairs with the class of the Renderer used to render them. */ - public ArrayList> getSeriesRegistry() { + public SeriesRegistry getSeriesRegistry() { return seriesRegistry; } @@ -139,7 +139,8 @@ public enum RenderMode { private HashMap, RendererType> renderers; - private ArrayList> seriesRegistry; + //private ArrayList> seriesRegistry; + private SeriesRegistry seriesRegistry; private final ArrayList listeners; @@ -149,7 +150,7 @@ public enum RenderMode { { listeners = new ArrayList<>(); - seriesRegistry = new ArrayList<>(); + seriesRegistry = new SeriesRegistry<>(); renderers = new HashMap<>(); borderPaint = new Paint(); @@ -553,7 +554,7 @@ public synchronized boolean addSeries(SeriesType series, FormatterType formatter * @return The {@link SeriesAndFormatter} that matches the series and rendererClass params, or null if one is not found. */ protected SeriesAndFormatter getSeries(SeriesType series, Class rendererClass) { - for(SeriesAndFormatter thisPair : getSeriesRegistry()) { + for(SeriesAndFormatter thisPair : seriesRegistry.asList()) { if(thisPair.getSeries() == series && thisPair.getFormatter().getRendererClass() == rendererClass) { return thisPair; } @@ -569,7 +570,7 @@ protected SeriesAndFormatter getSeries(SeriesType ser protected List> getSeries(SeriesType series) { List> results = new ArrayList>(); - for(SeriesAndFormatter thisPair : getSeriesRegistry()) { + for(SeriesAndFormatter thisPair : seriesRegistry.asList()) { if(thisPair.getSeries() == series) { results.add(thisPair); } diff --git a/androidplot-core/src/main/java/com/androidplot/SeriesRegistry.java b/androidplot-core/src/main/java/com/androidplot/SeriesRegistry.java new file mode 100644 index 00000000..d67c58cb --- /dev/null +++ b/androidplot-core/src/main/java/com/androidplot/SeriesRegistry.java @@ -0,0 +1,68 @@ +/* + * Copyright 2016 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; + +import com.androidplot.ui.Formatter; +import com.androidplot.ui.SeriesAndFormatter; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Created by halfhp on 1/16/16. + */ +public class SeriesRegistry { + + private ArrayList> sfPairs; + + { + sfPairs = new ArrayList<>(); + } + + public void add(SeriesAndFormatter sfPair) { + sfPairs.add(sfPair); + } + + public void remove(SeriesAndFormatter sfPair) { + sfPairs.remove(sfPair); + } + + public ArrayList> asList() { + return sfPairs; + } + + public Iterator> iterator() { + return sfPairs.iterator(); + } + + public List getSeriesList() { + List result = new ArrayList<>(); + for(SeriesAndFormatter sfPair : sfPairs) { + result.add(sfPair.getSeries()); + } + return result; + } + + public boolean isEmpty() { + return sfPairs.isEmpty(); + } + + public int size() { + return sfPairs.size(); + } +} diff --git a/androidplot-core/src/main/java/com/androidplot/candlestick/CandlestickFormatter.java b/androidplot-core/src/main/java/com/androidplot/candlestick/CandlestickFormatter.java new file mode 100644 index 00000000..fbebf711 --- /dev/null +++ b/androidplot-core/src/main/java/com/androidplot/candlestick/CandlestickFormatter.java @@ -0,0 +1,130 @@ +/* + * Copyright 2016 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.candlestick; + +import android.graphics.Color; +import android.graphics.Paint; +import com.androidplot.ui.SeriesRenderer; +import com.androidplot.util.PixelUtils; +import com.androidplot.xy.XYPlot; +import com.androidplot.xy.XYRegionFormatter; +import com.androidplot.xy.XYSeriesFormatter; + +/** + * Format for drawing a value in a {@link CandlestickSeries}. + */ +public class CandlestickFormatter extends XYSeriesFormatter { + + private Paint wickPaint = getDefaultStrokePaint(1.5f, Color.GREEN); + private Paint bodyFillPaint = getDefaultFillPaint(Color.YELLOW); + private Paint bodyStrokePaint = getDefaultStrokePaint(1.5f, Color.GREEN); + private Paint upperCapPaint = getDefaultStrokePaint(1.5f, Color.GREEN); + private Paint lowerCapPaint = getDefaultStrokePaint(1.5f, Color.GREEN); + + private float bodyWidth = PixelUtils.dpToPix(10f); + private float upperCapWidth = PixelUtils.dpToPix(10f); + private float lowerCapWidth = PixelUtils.dpToPix(10f); + + protected static Paint getDefaultFillPaint(int color) { + Paint p = new Paint(); + p.setStyle(Paint.Style.FILL); + p.setColor(color); + return p; + } + + protected static Paint getDefaultStrokePaint(float strokeDp, int color) { + Paint p = new Paint(); + p.setStyle(Paint.Style.STROKE); + p.setStrokeWidth(PixelUtils.dpToPix(strokeDp)); + p.setColor(color); + return p; + } + + @Override + public Class getRendererClass() { + return CandlestickRenderer.class; + } + + @Override + public SeriesRenderer getRendererInstance(XYPlot plot) { + return new CandlestickRenderer(plot); + } + + public Paint getWickPaint() { + return wickPaint; + } + + public void setWickPaint(Paint wickPaint) { + this.wickPaint = wickPaint; + } + + public Paint getBodyFillPaint() { + return bodyFillPaint; + } + + public void setBodyFillPaint(Paint bodyFillPaint) { + this.bodyFillPaint = bodyFillPaint; + } + + public Paint getBodyStrokePaint() { + return bodyStrokePaint; + } + + public void setBodyStrokePaint(Paint bodyStrokePaint) { + this.bodyStrokePaint = bodyStrokePaint; + } + + public Paint getUpperCapPaint() { + return upperCapPaint; + } + + public void setUpperCapPaint(Paint upperCapPaint) { + this.upperCapPaint = upperCapPaint; + } + + public Paint getLowerCapPaint() { + return lowerCapPaint; + } + + public void setLowerCapPaint(Paint lowerCapPaint) { + this.lowerCapPaint = lowerCapPaint; + } + + public float getBodyWidth() { + return bodyWidth; + } + + public void setBodyWidth(float bodyWidth) { + this.bodyWidth = bodyWidth; + } + + public float getLowerCapWidth() { + return lowerCapWidth; + } + + public void setLowerCapWidth(float lowerCapWidth) { + this.lowerCapWidth = lowerCapWidth; + } + + public float getUpperCapWidth() { + return upperCapWidth; + } + + public void setUpperCapWidth(float upperCapWidth) { + this.upperCapWidth = upperCapWidth; + } +} diff --git a/androidplot-core/src/main/java/com/androidplot/candlestick/CandlestickRenderer.java b/androidplot-core/src/main/java/com/androidplot/candlestick/CandlestickRenderer.java new file mode 100644 index 00000000..e26e3ab1 --- /dev/null +++ b/androidplot-core/src/main/java/com/androidplot/candlestick/CandlestickRenderer.java @@ -0,0 +1,115 @@ +/* + * Copyright 2016 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.candlestick; + +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.util.ValPixConverter; +import com.androidplot.xy.XYPlot; +import com.androidplot.xy.XYSeriesRenderer; + +/** + * Renders {@link CandlestickSeries} data into an {@link com.androidplot.xy.XYPlot}. + */ +public class CandlestickRenderer extends XYSeriesRenderer { + + public CandlestickRenderer(XYPlot plot) { + super(plot); + } + + @Override + public void onRender(Canvas canvas, RectF plotArea, CandlestickSeries series, FormatterType formatter, RenderStack stack) + throws PlotRenderException { + for(int i = 0; i < series.size(); i++) { + Number y = series.getY(i); + Number x = series.getX(i); + Number z = series.getZ(i); + Number a = series.getA(i); + Number b = series.getB(i); + drawValue(canvas, plotArea, formatter, x, y, z, a, b); + } + } + + protected void drawValue(Canvas canvas, RectF plotArea, FormatterType formatter, + Number x, Number y, Number z, Number a, Number b) { + final PointF yPix = ValPixConverter.valToPix( + x, y, + plotArea, + getPlot().getCalculatedMinX(), + getPlot().getCalculatedMaxX(), + getPlot().getCalculatedMinY(), + getPlot().getCalculatedMaxY()); + + final PointF zPix = ValPixConverter.valToPix( + x, z, + plotArea, + getPlot().getCalculatedMinX(), + getPlot().getCalculatedMaxX(), + getPlot().getCalculatedMinY(), + getPlot().getCalculatedMaxY()); + + final PointF aPix = ValPixConverter.valToPix( + x, a, + plotArea, + getPlot().getCalculatedMinX(), + getPlot().getCalculatedMaxX(), + getPlot().getCalculatedMinY(), + getPlot().getCalculatedMaxY()); + + final PointF bPix = ValPixConverter.valToPix( + x, b, + plotArea, + getPlot().getCalculatedMinX(), + getPlot().getCalculatedMaxX(), + getPlot().getCalculatedMinY(), + getPlot().getCalculatedMaxY()); + + drawWick(canvas, zPix, yPix, formatter); + drawBody(canvas, bPix, aPix, formatter); + drawUpperCap(canvas, yPix, formatter); + drawLowerCap(canvas, zPix, formatter); + } + + protected void drawWick(Canvas canvas, PointF min, PointF max, FormatterType formatter) { + canvas.drawLine(min.x, min.y, max.x, max.y, formatter.getWickPaint()); + } + + protected void drawBody(Canvas canvas, PointF min, PointF max, FormatterType formatter) { + final float halfWidth = formatter.getBodyWidth() / 2; + final RectF rect = new RectF(min.x - halfWidth, min.y, max.x + halfWidth, max.y); + canvas.drawRect(rect, formatter.getBodyFillPaint()); + canvas.drawRect(rect, formatter.getBodyStrokePaint()); + } + + protected void drawUpperCap(Canvas canvas, PointF val, FormatterType formatter) { + final float halfWidth = formatter.getUpperCapWidth(); + canvas.drawLine(val.x - halfWidth, val.y, val.x + halfWidth, val.y, formatter.getUpperCapPaint()); + } + + protected void drawLowerCap(Canvas canvas, PointF val, FormatterType formatter) { + final float halfWidth = formatter.getLowerCapWidth(); + canvas.drawLine(val.x - halfWidth, val.y, val.x + halfWidth, val.y, formatter.getLowerCapPaint()); + } + + @Override + protected void doDrawLegendIcon(Canvas canvas, RectF rect, FormatterType formatter) { + // TODO + } +} diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYGraphBounds.java b/androidplot-core/src/main/java/com/androidplot/candlestick/CandlestickSeries.java similarity index 55% rename from androidplot-core/src/main/java/com/androidplot/xy/XYGraphBounds.java rename to androidplot-core/src/main/java/com/androidplot/candlestick/CandlestickSeries.java index b0bc295b..bf858123 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYGraphBounds.java +++ b/androidplot-core/src/main/java/com/androidplot/candlestick/CandlestickSeries.java @@ -1,27 +1,38 @@ -/* - * 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.xy; - -public class XYGraphBounds { - - - private Number minX; - private Number maxX; - private Number minY; - private Number maxY; - -} +/* + * Copyright 2016 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.candlestick; + +import com.androidplot.xy.XYSeries; + +/** + * Series data for a candlestick chart. + * Variables: + * X - x value + * Y - max value + * Z - min value + * A - open value + * B - close value + * For a description of candlestick charts see: https://en.wikipedia.org/wiki/Candlestick_chart + */ +public interface CandlestickSeries extends XYSeries { + + Number getA(int index); + + Number getB(int index); + + Number getZ(int index); +} diff --git a/androidplot-core/src/main/java/com/androidplot/candlestick/SimpleCandlestickSeries.java b/androidplot-core/src/main/java/com/androidplot/candlestick/SimpleCandlestickSeries.java new file mode 100644 index 00000000..ac42e895 --- /dev/null +++ b/androidplot-core/src/main/java/com/androidplot/candlestick/SimpleCandlestickSeries.java @@ -0,0 +1,117 @@ +/* + * Copyright 2016 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.candlestick; + +import com.androidplot.Bounds; +import com.androidplot.util.SeriesUtils; +import com.androidplot.xy.XYBounds; + +import java.util.ArrayList; +import java.util.List; + +/** + * An immutable bare-bones implementation of {@link CandlestickSeries}. + */ +public class SimpleCandlestickSeries implements CandlestickSeries { + + private List xVals; + private List yVals; + private List zVals; + private List aVals; + private List bVals; + + //private XYBounds bounds; + + private String title; + + /** + * @param xVals xValue of the element. May be null; if null then i will be implicitly used. + * @param yVals + * @param zVals + * @param aVals + * @param bVals + */ + public SimpleCandlestickSeries(List xVals, List yVals, + List zVals, List aVals, List bVals, String title) { + final int size = yVals.size(); + if (zVals.size() != size || aVals.size() != size || + bVals.size() != size || (xVals != null && xVals.size() != size)) { + throw new RuntimeException("All list params must be of the same length."); + } + this.title = title; + this.xVals = xVals; + this.yVals = yVals; + this.zVals = zVals; + this.aVals = aVals; + this.bVals = bVals; + //bounds = new XYBounds(); + if (this.xVals == null) { + this.xVals = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + this.xVals.add(i); + } + // we generated the xVals so we already know the min/max: + //bounds.setMinX(0); + //bounds.setMaxX(size - 1); + } +// } else { +// bounds.setXBounds(SeriesUtils.minMax(xVals)); +// } +// +// bounds.setYBounds(SeriesUtils.minMax(yVals, zVals)); + } + + @Override + public Number getA(int index) { + return aVals.get(index); + } + + @Override + public Number getB(int index) { + return bVals.get(index); + } + + @Override + public Number getZ(int index) { + return zVals.get(index); + } + + @Override + public int size() { + return yVals.size(); + } + + @Override + public Number getX(int index) { + return xVals.get(index); + } + + @Override + public Number getY(int index) { + return yVals.get(index); + } + +// @Override +// public XYBounds getBounds() { +// return bounds; +// } + + @Override + public String getTitle() { + return title; + } +} 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 ff570475..542a0e91 100644 --- a/androidplot-core/src/main/java/com/androidplot/ui/RenderStack.java +++ b/androidplot-core/src/main/java/com/androidplot/ui/RenderStack.java @@ -84,7 +84,7 @@ public void sync() { * TODO: rendering performance *might* be improved by reusing StackElement instances but I'm skeptical... */ getElements().clear(); - List> pairList = plot.getSeriesRegistry(); + List> pairList = plot.getSeriesRegistry().asList(); for(SeriesAndFormatter thisPair: pairList) { getElements().add(new StackElement<>(thisPair)); } diff --git a/androidplot-core/src/main/java/com/androidplot/ui/SeriesRenderer.java b/androidplot-core/src/main/java/com/androidplot/ui/SeriesRenderer.java index d4105d8d..f3263309 100644 --- a/androidplot-core/src/main/java/com/androidplot/ui/SeriesRenderer.java +++ b/androidplot-core/src/main/java/com/androidplot/ui/SeriesRenderer.java @@ -96,7 +96,7 @@ public void drawSeriesLegendIcon(Canvas canvas, RectF rect, SeriesFormatterType */ public List> getSeriesList() { List> results = new ArrayList<>(); - ArrayList sfList = getPlot().getSeriesRegistry(); + ArrayList sfList = getPlot().getSeriesRegistry().asList(); for(SeriesAndFormatter thisPair : sfList) { if(thisPair.getFormatter().getRendererClass() == getClass()) { 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 9e51b6f0..d602b17d 100644 --- a/androidplot-core/src/main/java/com/androidplot/util/SeriesUtils.java +++ b/androidplot-core/src/main/java/com/androidplot/util/SeriesUtils.java @@ -16,26 +16,162 @@ package com.androidplot.util; +import android.util.Pair; +import com.androidplot.Bounds; +import com.androidplot.candlestick.CandlestickSeries; +import com.androidplot.xy.XYBounds; +import com.androidplot.xy.XYConstraints; import com.androidplot.xy.XYSeries; +import java.util.List; + /** - * Created by nick_f on 7/24/14. + * Utilities for dealing with Series data. */ public class SeriesUtils { /** - * - * @param series - * @return The largest yVal in the series or null if the series contains no non-null yVals. + * @param constraints may be null. + * @param seriesList + * @return + * @since 0.9.7 + */ + public static XYBounds minMax(XYConstraints constraints, List seriesList) { + // TODO: this is inefficient...clean it up! + return minMax(constraints, seriesList.toArray(new XYSeries[seriesList.size()])); + } + + /** + * @param constraints May be null. + * @param seriesArray + * @return + * @since 0.9.7 + */ + public static XYBounds minMax(XYConstraints constraints, XYSeries... seriesArray) { + + final XYBounds bounds = new XYBounds(); + + // make sure there is series data to iterate over: + if (seriesArray != null && seriesArray.length > 0) { + + // iterate over each series + for (XYSeries series : seriesArray) { + if (series.size() > 0) { + for (int i = 0; i < series.size(); i++) { + final Number xi = series.getX(i); + final Number yi = series.getY(i); + + // if constraints have been set, make sure this xy coordinate exists within them: + if (constraints == null || constraints.contains(xi, yi)) { + if (xi != null) { + if (bounds.getMinX() == null || + xi.doubleValue() < bounds.getMinX().doubleValue()) { + bounds.setMinX(xi); + } + if (bounds.getMaxX() == null || + xi.doubleValue() > bounds.getMaxX().doubleValue()) { + bounds.setMaxX(xi); + } + } + if (yi != null) { + if (series instanceof CandlestickSeries) { + // TODO + throw new UnsupportedOperationException("Not yet implemented."); + } else { + if (bounds.getMinY() == null || + yi.doubleValue() < bounds.getMinY().doubleValue()) { + bounds.setMinY(yi); + } + } + if (series instanceof CandlestickSeries) { + // TODO + throw new UnsupportedOperationException("Not yet implemented."); + } else { + if (bounds.getMaxY() == null || + yi.doubleValue() > bounds.getMaxY().doubleValue()) { + bounds.setMaxY(yi); + } + } + } + } + } + } + } + } + return bounds; + } + + /** + * @param lists + * @return + * @since 0.9.7 */ - public static Number getMaxY(XYSeries series) { + public static Bounds minMax(List... lists) { + Number min = null; Number max = null; - for(int i = 0; i < series.size(); i++) { - Number thisNumber = series.getY(i); - if(max == null || thisNumber != null && thisNumber.doubleValue() > max.doubleValue()) { - max = thisNumber; + for (List list : lists) { + for (Number i : list) { + if (i != null) { + double di = i.doubleValue(); + if (min == null || di < min.doubleValue()) { + min = i; + } + if (max == null || di > max.doubleValue()) { + max = i; + } + } + } + } + return new Bounds(min, max); + } + + + Number min(Number... numbers) { + Number min = null; + for(Number number : numbers) { + if(min == null || number != null && number.doubleValue() < min.doubleValue()) { + min = number; + } + } + return min; + } + + Number max(Number... numbers) { + Number max = null; + for(Number number : numbers) { + if(max == null || number != null && number.doubleValue() > max.doubleValue()) { + max = number; } } return max; } + +// Pair minMaxY(CandlestickSeries series, int index) { +// Number min = series.getY(index); +// Number max = series.getY(index); +// +// final Number a = series.getA(index); +// +// if(a != null) { +// double ad = a.doubleValue(); +// if(min == null || ad < min.doubleValue()) { +// min = ad; +// } else if(max == null || ad > max.doubleValue()) { +// max = ad; +// } +// } +// +// final Number z = series.getZ(index); +// +// if(z != null) { +// double zd = z.doubleValue(); +// if(min == null || zd < min.doubleValue()) { +// min = zd; +// } else if(max == null || zd > max.doubleValue()) { +// max = zd; +// } +// } +// +// return new Pair<>(min, max); +// } } 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 5279a1a6..6af973b5 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/BarRenderer.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/BarRenderer.java @@ -34,7 +34,7 @@ /** * Renders the points in an XYSeries as bars. */ -public class BarRenderer extends XYSeriesRenderer { +public class BarRenderer extends XYSeriesRenderer { private BarRenderStyle renderStyle = BarRenderStyle.OVERLAID; // default Render Style private BarWidthStyle widthStyle = BarWidthStyle.FIXED_WIDTH; // default Width Style diff --git a/androidplot-core/src/main/java/com/androidplot/xy/BoundaryMode.java b/androidplot-core/src/main/java/com/androidplot/xy/BoundaryMode.java index 47c4fe51..c0b791a6 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/BoundaryMode.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/BoundaryMode.java @@ -20,7 +20,7 @@ public enum BoundaryMode { FIXED, AUTO, GROW, - SHRINNK + SHRINK } 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 29a37b85..02b033f5 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java @@ -27,7 +27,7 @@ * Renders a point as a line with the vertices marked. Requires 2 or more points to * be rendered. */ -public class LineAndPointRenderer extends XYSeriesRenderer { +public class LineAndPointRenderer extends XYSeriesRenderer { protected static final int ZERO = 0; protected static final int ONE = 1; 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 48228448..c406d9e2 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/RectRegion.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/RectRegion.java @@ -62,12 +62,10 @@ public boolean containsValue(Number x, Number y) { } public boolean containsDomainValue(Number value) { - //return RectRegion.isBetween(value, minX, maxX); return xLineRegion.contains(value); } public boolean containsRangeValue(Number value) { - //return RectRegion.isBetween(value, minY, maxY); return yLineRegion.contains(value); } diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYBounds.java b/androidplot-core/src/main/java/com/androidplot/xy/XYBounds.java new file mode 100644 index 00000000..5e2f6062 --- /dev/null +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYBounds.java @@ -0,0 +1,97 @@ +/* + * 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.xy; + +import com.androidplot.Bounds; + +/** + * Defines a rectangle using xy min/max values. XYBounds differs from {@link RectRegion} in that + * it accepts null values. + */ +public class XYBounds { + + private Bounds xBounds; + private Bounds yBounds; + + public XYBounds() { + this(null, null, null, null); + } + + public XYBounds(Number minX, Number maxX, Number minY, Number maxY) { + xBounds = new Bounds(minX, maxX); + yBounds = new Bounds(minY, maxY); + } + + /** + * Compares the input bounds xy min/max vals against this instance's current xy min/max vals. + * If the input.min is less than this.min then this.min will be set to input.min. + * If the input.max is greater than this.max then this.max will be set to input.max + * @param input + */ + public void union(XYBounds input) { + xBounds.union(input.getXBounds()); + yBounds.union(input.getYBounds()); + } + + public void setXBounds(Bounds xBounds) { + this.xBounds = xBounds; + } + + public void setYBounds(Bounds yBounds) { + this.yBounds = yBounds; + } + + public Bounds getXBounds() { + return xBounds; + } + + public Bounds getYBounds() { + return yBounds; + } + + public Number getMinX() { + return xBounds.getMin(); + } + + public void setMinX(Number minX) { + xBounds.setMin(minX); + } + + public Number getMaxX() { + return xBounds.getMax(); + } + + public void setMaxX(Number maxX) { + xBounds.setMax(maxX); + } + + public Number getMinY() { + return yBounds.getMin(); + } + + public void setMinY(Number minY) { + yBounds.setMin(minY); + } + + public Number getMaxY() { + return yBounds.getMax(); + } + + public void setMaxY(Number maxY) { + yBounds.setMax(maxY); + } +} diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYConstraints.java b/androidplot-core/src/main/java/com/androidplot/xy/XYConstraints.java new file mode 100644 index 00000000..00f50bdc --- /dev/null +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYConstraints.java @@ -0,0 +1,155 @@ +/* + * Copyright 2016 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.xy; + +/** + * Calculates the min/max constraints for an xy plane. + */ +public class XYConstraints { + + // used for calculating the domain/range extents that will be displayed on the plot. + // using boundaries and origins are mutually exclusive. because of this, + // setting one will disable the other. when only setting the FramingModel, + // the origin or boundary is set to the current value of the plot. + private XYFramingModel domainFramingModel = XYFramingModel.EDGE; + private XYFramingModel rangeFramingModel = XYFramingModel.EDGE; + + // determines how boundaries adjust as new min/max values are encountered: + private BoundaryMode domainUpperBoundaryMode = BoundaryMode.AUTO; + private BoundaryMode domainLowerBoundaryMode = BoundaryMode.AUTO; + private BoundaryMode rangeUpperBoundaryMode = BoundaryMode.AUTO; + private BoundaryMode rangeLowerBoundaryMode = BoundaryMode.AUTO; + + private Number minX; + private Number maxX; + private Number minY; + private Number maxY; + + public XYConstraints() { + this(null, null, null, null); + } + + public XYConstraints(Number minX, Number maxX, Number minY, Number maxY) { + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + public boolean contains(Number x, Number y) { + 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; + } + } + + public Number getMinX() { + return minX; + } + + public Number getMaxX() { + return maxX; + } + + public Number getMinY() { + return minY; + } + + public Number getMaxY() { + return maxY; + } + + public XYFramingModel getDomainFramingModel() { + return domainFramingModel; + } + + public void setDomainFramingModel(XYFramingModel domainFramingModel) { + this.domainFramingModel = domainFramingModel; + } + + public XYFramingModel getRangeFramingModel() { + return rangeFramingModel; + } + + public void setRangeFramingModel(XYFramingModel rangeFramingModel) { + this.rangeFramingModel = rangeFramingModel; + } + + public BoundaryMode getDomainUpperBoundaryMode() { + return domainUpperBoundaryMode; + } + + public void setDomainUpperBoundaryMode(BoundaryMode domainUpperBoundaryMode) { + this.domainUpperBoundaryMode = domainUpperBoundaryMode; + } + + public BoundaryMode getDomainLowerBoundaryMode() { + return domainLowerBoundaryMode; + } + + public void setDomainLowerBoundaryMode(BoundaryMode domainLowerBoundaryMode) { + this.domainLowerBoundaryMode = domainLowerBoundaryMode; + } + + public BoundaryMode getRangeUpperBoundaryMode() { + return rangeUpperBoundaryMode; + } + + public void setRangeUpperBoundaryMode(BoundaryMode rangeUpperBoundaryMode) { + this.rangeUpperBoundaryMode = rangeUpperBoundaryMode; + } + + public BoundaryMode getRangeLowerBoundaryMode() { + return rangeLowerBoundaryMode; + } + + public void setRangeLowerBoundaryMode(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; + } +} 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 c4fa1572..66a935c8 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYLegendWidget.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYLegendWidget.java @@ -171,7 +171,7 @@ protected synchronized void doOnDraw(Canvas canvas, RectF widgetRect) { RectF cellRect; // draw each series legend item: - for(SeriesAndFormatter sfPair : plot.getSeriesRegistry()) { + for(SeriesAndFormatter sfPair : plot.getSeriesRegistry().asList()) { cellRect = it.next(); XYSeriesFormatter format = sfPair.getFormatter(); drawSeriesLegendCell(canvas, plot.getRenderer(sfPair.getFormatter().getRendererClass()), 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 19bae858..359e9e60 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java @@ -30,6 +30,8 @@ import com.androidplot.ui.widget.TextLabelWidget; import com.androidplot.util.AttrUtils; import com.androidplot.util.PixelUtils; +import com.androidplot.util.SeriesUtils; + import java.text.Format; import java.util.ArrayList; import java.util.Arrays; @@ -81,19 +83,10 @@ public class XYPlot extends Plot private TextLabelWidget domainLabelWidget; private TextLabelWidget rangeLabelWidget; - //private XYStepMode domainStepMode = XYStepMode.SUBDIVIDE; - //private double domainStepValue = 10; - - //private XYStepMode rangeStepMode = XYStepMode.SUBDIVIDE; - //private double rangeStepValue = 10; private XYStepModel domainStepModel; private XYStepModel rangeStepModel; - // user settable min/max values - private Number userMinX; - private Number userMaxX; - private Number userMinY; - private Number userMaxY; + private XYConstraints constraints = new XYConstraints(); // these are the final min/max used for dispplaying data private Number calculatedMinX; @@ -119,13 +112,6 @@ public class XYPlot extends Plot private Number domainRightMin = null; private Number domainRightMax = null; - // used for calculating the domain/range extents that will be displayed on the plot. - // using boundaries and origins are mutually exclusive. because of this, - // setting one will disable the other. when only setting the FramingModel, - // the origin or boundary is set to the current value of the plot. - private XYFramingModel domainFramingModel = XYFramingModel.EDGE; - private XYFramingModel rangeFramingModel = XYFramingModel.EDGE; - private Number userDomainOrigin; private Number userRangeOrigin; @@ -137,11 +123,6 @@ public class XYPlot extends Plot @SuppressWarnings("FieldCanBeLocal") private Number rangeOriginExtent = null; - private BoundaryMode domainUpperBoundaryMode = BoundaryMode.AUTO; - private BoundaryMode domainLowerBoundaryMode = BoundaryMode.AUTO; - private BoundaryMode rangeUpperBoundaryMode = BoundaryMode.AUTO; - private BoundaryMode rangeLowerBoundaryMode = BoundaryMode.AUTO; - private boolean drawDomainOriginEnabled = true; private boolean drawRangeOriginEnabled = true; @@ -292,7 +273,6 @@ protected void processAttrs(TypedArray attrs) { AttrUtils.configureStep(attrs, getRangeStepModel(), R.styleable.xy_XYPlot_rangeStepMode, R.styleable.xy_XYPlot_rangeStep); - // domainLabel size & position AttrUtils.configureWidget(attrs, getDomainLabelWidget(), R.styleable.xy_XYPlot_domainLabelHeightSizeLayoutType, R.styleable.xy_XYPlot_domainLabelHeight, @@ -436,118 +416,74 @@ public Number getXVal(PointF point) { return getGraphWidget().getXVal(point); } - private boolean isXValWithinView(double xVal) { - return (userMinY == null || xVal >= userMinY.doubleValue()) && - userMaxY == null || xVal <= userMaxY.doubleValue(); - } - - private boolean isPointVisible(Number x, Number y) { - // values without both an x and y val arent visible - if (x == null || y == null) { - return false; - } - return isValWithinRange(y.doubleValue(), userMinY, userMaxY) && - isValWithinRange(x.doubleValue(), userMinX, userMaxX); - } - - private boolean isValWithinRange(double val, Number min, Number max) { - boolean isAboveMinThreshold = min == null || val >= min.doubleValue(); - boolean isBelowMaxThreshold = max == null || val <= max.doubleValue(); - return isAboveMinThreshold && - isBelowMaxThreshold; - } - public void calculateMinMaxVals() { prevMinX = calculatedMinX; prevMaxX = calculatedMaxX; prevMinY = calculatedMinY; prevMaxY = calculatedMaxY; - calculatedMinX = userMinX; - calculatedMaxX = userMaxX; - calculatedMinY = userMinY; - calculatedMaxY = userMaxY; - - // next we go through each series to update our min/max values: - for (SeriesAndFormatter thisPair : getSeriesRegistry()) { - XYSeries series = thisPair.getSeries(); - // step through each point in each series: - for (int i = 0; i < series.size(); i++) { - Number thisX = series.getX(i); - Number thisY = series.getY(i); - if (isPointVisible(thisX, thisY)) { - // only calculate if a static value has not been set: - if (userMinX == null) { - if (thisX != null && (calculatedMinX == null || - thisX.doubleValue() < calculatedMinX.doubleValue())) { - calculatedMinX = thisX; - } - } - - if (userMaxX == null) { - if (thisX != null && (calculatedMaxX == null || - thisX.doubleValue() > calculatedMaxX.doubleValue())) { - calculatedMaxX = thisX; - } - } - - if (userMinY == null) { - if (thisY != null && (calculatedMinY == null || - thisY.doubleValue() < calculatedMinY.doubleValue())) { - calculatedMinY = thisY; - } - } - - if (userMaxY == null) { - if (thisY != null && (calculatedMaxY == null || thisY.doubleValue() > calculatedMaxY.doubleValue())) { - calculatedMaxY = thisY; - } - } - } - } + calculatedMinX = constraints.getMinX(); + calculatedMaxX = constraints.getMaxX(); + calculatedMinY = constraints.getMinY(); + calculatedMaxY = constraints.getMaxY(); + + // only calculate if we must: + if(calculatedMinX == null || calculatedMaxX == null || calculatedMinY == null || calculatedMaxY == null) { + + XYBounds bounds = SeriesUtils.minMax(constraints, getSeriesRegistry().getSeriesList()); + + if(calculatedMinX == null) calculatedMinX = bounds.getMinX(); + if(calculatedMaxX == null) calculatedMaxX = bounds.getMaxX(); + if(calculatedMinY == null) calculatedMinY = bounds.getMinY(); + if(calculatedMaxY == null) calculatedMaxY = bounds.getMaxY(); } // at this point we now know what points are going to be visible on our // plot, but we still need to make corrections based on modes being used: // (grow, shrink etc.) - switch (domainFramingModel) { + switch (constraints.getDomainFramingModel()) { case ORIGIN: updateDomainMinMaxForOriginModel(); break; case EDGE: - calculatedMaxX = getCalculatedUpperBoundary(domainUpperBoundaryMode, prevMaxX, calculatedMaxX); - calculatedMinX = getCalculatedLowerBoundary(domainLowerBoundaryMode, prevMinX, calculatedMinX); - calculatedMinX = ApplyUserMinMax(calculatedMinX, domainLeftMin, + calculatedMaxX = getCalculatedUpperBoundary( + constraints.getDomainUpperBoundaryMode(), prevMaxX, calculatedMaxX); + calculatedMinX = getCalculatedLowerBoundary( + constraints.getDomainLowerBoundaryMode(), prevMinX, calculatedMinX); + calculatedMinX = applyUserMinMax(calculatedMinX, domainLeftMin, domainLeftMax); - calculatedMaxX = ApplyUserMinMax(calculatedMaxX, + calculatedMaxX = applyUserMinMax(calculatedMaxX, domainRightMin, domainRightMax); break; default: throw new UnsupportedOperationException( - "Domain Framing Model not yet supported: " + domainFramingModel); + "Domain Framing Model not yet supported: " + constraints.getDomainFramingModel()); } - switch (rangeFramingModel) { + switch (constraints.getDomainFramingModel()) { case ORIGIN: updateRangeMinMaxForOriginModel(); break; case EDGE: if (getSeriesRegistry().size() > 0) { - calculatedMaxY = getCalculatedUpperBoundary(rangeUpperBoundaryMode, prevMaxY, calculatedMaxY); - calculatedMinY = getCalculatedLowerBoundary(rangeLowerBoundaryMode, prevMinY, calculatedMinY); - calculatedMinY = ApplyUserMinMax(calculatedMinY, - rangeBottomMin, rangeBottomMax); - calculatedMaxY = ApplyUserMinMax(calculatedMaxY, rangeTopMin, - rangeTopMax); + calculatedMaxY = getCalculatedUpperBoundary( + constraints.getRangeUpperBoundaryMode(), prevMaxY, calculatedMaxY); + calculatedMinY = getCalculatedLowerBoundary( + constraints.getRangeLowerBoundaryMode(), prevMinY, calculatedMinY); + calculatedMinY = applyUserMinMax(calculatedMinY, rangeBottomMin, rangeBottomMax); + calculatedMaxY = applyUserMinMax(calculatedMaxY, rangeTopMin, rangeTopMax); } break; default: throw new UnsupportedOperationException( - "Range Framing Model not yet supported: " + domainFramingModel); + "Range Framing Model not yet supported: " + constraints.getRangeFramingModel()); } - calculatedDomainOrigin = userDomainOrigin != null ? userDomainOrigin : getCalculatedMinX(); - calculatedRangeOrigin = this.userRangeOrigin != null ? userRangeOrigin : getCalculatedMinY(); + calculatedDomainOrigin = userDomainOrigin != null ? + userDomainOrigin : getCalculatedMinX(); + + calculatedRangeOrigin = this.userRangeOrigin != null ? + userRangeOrigin : getCalculatedMinY(); } protected Number getCalculatedUpperBoundary(BoundaryMode mode, Number previousMax, Number calculatedMax) { @@ -561,7 +497,7 @@ protected Number getCalculatedUpperBoundary(BoundaryMode mode, Number previousMa calculatedMax = previousMax; } break; - case SHRINNK: + case SHRINK: if (!(previousMax == null || calculatedMax.doubleValue() < previousMax.doubleValue())) { calculatedMax = previousMax; } @@ -583,7 +519,7 @@ protected Number getCalculatedLowerBoundary(BoundaryMode mode, Number previousMi return previousMin; } break; - case SHRINNK: + case SHRINK: if (!(previousMin == null || calculatedMin.doubleValue() > previousMin.doubleValue())) { return previousMin; } @@ -602,7 +538,7 @@ protected Number getCalculatedLowerBoundary(BoundaryMode mode, Number previousMi * @param min * @param max */ - private Number ApplyUserMinMax(Number value, Number min, Number max) { + private Number applyUserMinMax(Number value, Number min, Number max) { value = (((min == null) || (value == null) || (value.doubleValue() > min.doubleValue())) ? value : min); @@ -633,14 +569,14 @@ public void centerOnDomainOrigin(Number origin, Number extent, BoundaryMode mode if (origin == null) { throw new NullPointerException("Origin param cannot be null."); } - domainFramingModel = XYFramingModel.ORIGIN; + constraints.setDomainFramingModel(XYFramingModel.ORIGIN); setUserDomainOrigin(origin); domainOriginExtent = extent; domainOriginBoundaryMode = mode; Number[] minMax = getOriginMinMax(domainOriginBoundaryMode, userDomainOrigin, domainOriginExtent); - userMinX = minMax[0]; - userMaxX = minMax[1]; + constraints.setMinX(minMax[0]); + constraints.setMaxX(minMax[1]); } /** @@ -665,14 +601,14 @@ public void centerOnRangeOrigin(Number origin, Number extent, BoundaryMode mode) if (origin == null) { throw new NullPointerException("Origin param cannot be null."); } - rangeFramingModel = XYFramingModel.ORIGIN; + constraints.setRangeFramingModel(XYFramingModel.ORIGIN); setUserRangeOrigin(origin); rangeOriginExtent = extent; rangeOriginBoundaryMode = mode; Number[] minMax = getOriginMinMax(rangeOriginBoundaryMode, userRangeOrigin, rangeOriginExtent); - userMinY = minMax[0]; - userMaxY = minMax[1]; + constraints.setMinY(minMax[0]); + constraints.setMaxY(minMax[1]); } /** @@ -739,7 +675,7 @@ public void updateDomainMinMaxForOriginModel() { } } break; - case SHRINNK: + case SHRINK: if (prevMinX == null || dlb > prevMinX.doubleValue()) { calculatedMinX = dlb; } else { @@ -773,7 +709,7 @@ public void updateRangeMinMaxForOriginModel() { break; case FIXED: case GROW: - case SHRINNK: + case SHRINK: default: throw new UnsupportedOperationException( "Range Origin Boundary Mode not yet supported: " + rangeOriginBoundaryMode); @@ -997,11 +933,11 @@ public synchronized void setRangeBoundaries(Number lowerBoundary, BoundaryMode l } protected synchronized void setDomainUpperBoundaryMode(BoundaryMode mode) { - this.domainUpperBoundaryMode = mode; + constraints.setDomainUpperBoundaryMode(mode); } - protected synchronized void setUserMaxX(Number boundary) { - this.userMaxX = boundary; + protected synchronized void setUserMaxX(Number maxX) { + constraints.setMaxX(maxX); } /** @@ -1017,11 +953,11 @@ public synchronized void setDomainUpperBoundary(Number boundary, BoundaryMode mo } protected synchronized void setDomainLowerBoundaryMode(BoundaryMode mode) { - this.domainLowerBoundaryMode = mode; + constraints.setDomainLowerBoundaryMode(mode); } - protected synchronized void setUserMinX(Number boundary) { - this.userMinX = boundary; + protected synchronized void setUserMinX(Number minX) { + constraints.setMinX(minX); } /** @@ -1037,11 +973,11 @@ public synchronized void setDomainLowerBoundary(Number boundary, BoundaryMode mo } protected synchronized void setRangeUpperBoundaryMode(BoundaryMode mode) { - this.rangeUpperBoundaryMode = mode; + constraints.setRangeUpperBoundaryMode(mode); } - protected synchronized void setUserMaxY(Number boundary) { - this.userMaxY = boundary; + protected synchronized void setUserMaxY(Number maxY) { + constraints.setMaxY(maxY); } /** @@ -1057,11 +993,11 @@ public synchronized void setRangeUpperBoundary(Number boundary, BoundaryMode mod } protected synchronized void setRangeLowerBoundaryMode(BoundaryMode mode) { - this.rangeLowerBoundaryMode = mode; + constraints.setRangeLowerBoundaryMode(mode); } - protected synchronized void setUserMinY(Number boundary) { - this.userMinY = boundary; + protected synchronized void setUserMinY(Number minY) { + constraints.setMinY(minY); } /** @@ -1076,22 +1012,6 @@ public synchronized void setRangeLowerBoundary(Number boundary, BoundaryMode mod setRangeFramingModel(XYFramingModel.EDGE); } - private Number getUserMinX() { - return userMinX; - } - - private Number getUserMaxX() { - return userMaxX; - } - - private Number getUserMinY() { - return userMinY; - } - - private Number getUserMaxY() { - return userMaxY; - } - public Number getDomainOrigin() { return calculatedDomainOrigin; } @@ -1100,22 +1020,6 @@ public Number getRangeOrigin() { return calculatedRangeOrigin; } - protected BoundaryMode getDomainUpperBoundaryMode() { - return domainUpperBoundaryMode; - } - - protected BoundaryMode getDomainLowerBoundaryMode() { - return domainLowerBoundaryMode; - } - - protected BoundaryMode getRangeUpperBoundaryMode() { - return rangeUpperBoundaryMode; - } - - protected BoundaryMode getRangeLowerBoundaryMode() { - return rangeLowerBoundaryMode; - } - public synchronized void setUserDomainOrigin(Number origin) { if (origin == null) { throw new NullPointerException("Origin value cannot be null."); @@ -1130,23 +1034,14 @@ public synchronized void setUserRangeOrigin(Number origin) { this.userRangeOrigin = origin; } - public XYFramingModel getDomainFramingModel() { - return domainFramingModel; - } - @SuppressWarnings("SameParameterValue") - protected void setDomainFramingModel(XYFramingModel domainFramingModel) { - this.domainFramingModel = domainFramingModel; - } - - public XYFramingModel getRangeFramingModel() { - - return rangeFramingModel; + protected void setDomainFramingModel(XYFramingModel model) { + constraints.setDomainFramingModel(model); } @SuppressWarnings("SameParameterValue") - protected void setRangeFramingModel(XYFramingModel rangeFramingModel) { - this.rangeFramingModel = rangeFramingModel; + protected void setRangeFramingModel(XYFramingModel model) { + constraints.setRangeFramingModel(model); } /** 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 517595f3..ae2bc93d 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYRegionFormatter.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYRegionFormatter.java @@ -25,7 +25,6 @@ */ public class XYRegionFormatter { - //private int color; private Paint paint = new Paint(); { @@ -48,11 +47,7 @@ public XYRegionFormatter(Context ctx, int xmlCfgId) { } public XYRegionFormatter(int color) { - //paint = new Paint(); paint.setColor(color); - //paint.setStyle(Paint.Style.FILL); - //paint.setAntiAlias(true); - //this.color = color; } public int getColor() { diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYSeries.java b/androidplot-core/src/main/java/com/androidplot/xy/XYSeries.java index a9053095..fbba584c 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYSeries.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYSeries.java @@ -16,7 +16,6 @@ package com.androidplot.xy; -import android.util.Pair; import com.androidplot.Series; /** @@ -27,7 +26,7 @@ public interface XYSeries extends Series { /** * @return Number of elements in this Series. */ - public int size(); + int size(); /** * Returns the x-value for an index within a series. @@ -37,7 +36,7 @@ public interface XYSeries extends Series { * * @return The x-value. */ - public Number getX(int index); + Number getX(int index); /** * Returns the y-value for an index within a series. @@ -47,5 +46,13 @@ public interface XYSeries extends Series { * * @return The y-value. */ - public Number getY(int index); + Number getY(int index); + +// /** +// * Optional method to optimize min/max value calculation. Implementations that do not wish to provide the +// * optimization should simply return null. +// * @return An instance of {@link XYBounds} containing xy min/max values. +// * @since 0.9.7 +// */ +// XYBounds getBounds(); } diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYSeriesRenderer.java b/androidplot-core/src/main/java/com/androidplot/xy/XYSeriesRenderer.java index a9d976a5..6bb46c9f 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYSeriesRenderer.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYSeriesRenderer.java @@ -26,8 +26,8 @@ * Base class for all Renderers that render XYSeries data. * @param */ -public abstract class XYSeriesRenderer - extends SeriesRenderer { +public abstract class XYSeriesRenderer + extends SeriesRenderer { public XYSeriesRenderer(XYPlot plot) { super(plot); @@ -39,8 +39,8 @@ public XYSeriesRenderer(XYPlot plot) { */ public Hashtable getUniqueRegionFormatters() { - Hashtable found = new Hashtable(); - for(SeriesAndFormatter sfPair : getSeriesList()) { + Hashtable found = new Hashtable<>(); + for(SeriesAndFormatter sfPair : getSeriesList()) { ZIndexable regionIndexer = sfPair.getFormatter().getRegions(); for (RectRegion region : regionIndexer.elements()) { XYRegionFormatter f = sfPair.getFormatter().getRegionFormatter(region); diff --git a/androidplot-core/src/test/java/com/androidplot/util/SeriesUtilsTest.java b/androidplot-core/src/test/java/com/androidplot/util/SeriesUtilsTest.java new file mode 100644 index 00000000..4697dd41 --- /dev/null +++ b/androidplot-core/src/test/java/com/androidplot/util/SeriesUtilsTest.java @@ -0,0 +1,130 @@ +/* + * Copyright 2016 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 com.androidplot.Bounds; +import com.androidplot.xy.SimpleXYSeries; +import com.androidplot.xy.XYBounds; +import org.junit.Test; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import static junit.framework.Assert.assertEquals; + +public class SeriesUtilsTest { + + // 8 element lists: + final List LINEAR = Arrays.asList(new Number[]{ 1, 2, 3, 4 ,5 , 6, 7, 8}); + final List LINEAR_INVERSE = Arrays.asList(new Number[]{ 8, 7, 6, 5, 4, 3, 2, 1}); + final List ZIG_ZAG = Arrays.asList(new Number[]{1, 10, 1, 10, 1, 10, 1, 10}); + final List NULLS = Arrays.asList(new Number[]{null, 2, null, 4, null, 0, -1, null}); + + // single element lists: + final List SINGLE_VALUE = Arrays.asList(new Number[]{3}); + final List SINGLE_VALUE_NULL = Arrays.asList(new Number[]{null}); + + // empty list: + final List EMPTY = new ArrayList<>(); + + @org.junit.After + public void tearDown() throws Exception { + + } + + @Test + public void testSeriesMinMax() { + SimpleXYSeries series = new SimpleXYSeries(LINEAR, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, null); + XYBounds minMax = SeriesUtils.minMax(series); + assertEquals(0, minMax.getMinX()); + assertEquals(7, minMax.getMaxX()); + assertEquals(1, minMax.getMinY()); + assertEquals(8, minMax.getMaxY()); + + series = new SimpleXYSeries(LINEAR_INVERSE, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, null); + minMax = SeriesUtils.minMax(series); + assertEquals(0, minMax.getMinX()); + assertEquals(7, minMax.getMaxX()); + assertEquals(1, minMax.getMinY()); + assertEquals(8, minMax.getMaxY()); + + series = new SimpleXYSeries(ZIG_ZAG, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, null); + minMax = SeriesUtils.minMax(series); + assertEquals(0, minMax.getMinX()); + assertEquals(7, minMax.getMaxX()); + assertEquals(1, minMax.getMinY()); + assertEquals(10, minMax.getMaxY()); + + series = new SimpleXYSeries(NULLS, NULLS, null); + minMax = SeriesUtils.minMax(series); + assertEquals(-1, minMax.getMinX()); + assertEquals(4, minMax.getMaxX()); + assertEquals(-1, minMax.getMinY()); + assertEquals(4, minMax.getMaxY()); + + series = new SimpleXYSeries(SINGLE_VALUE, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, null); + minMax = SeriesUtils.minMax(series); + assertEquals(0, minMax.getMinX()); + assertEquals(0, minMax.getMaxX()); + assertEquals(3, minMax.getMinY()); + assertEquals(3, minMax.getMaxY()); + + series = new SimpleXYSeries(SINGLE_VALUE_NULL, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, null); + minMax = SeriesUtils.minMax(series); + assertEquals(0, minMax.getMinX()); + assertEquals(0, minMax.getMaxX()); + assertEquals(null, minMax.getMinY()); + assertEquals(null, minMax.getMaxY()); + + series = new SimpleXYSeries(EMPTY, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, null); + minMax = SeriesUtils.minMax(series); + assertEquals(null, minMax.getMinX()); + assertEquals(null, minMax.getMaxX()); + assertEquals(null, minMax.getMinY()); + assertEquals(null, minMax.getMaxY()); + } + + @Test + public void testListMinMax() { + Bounds minMax = SeriesUtils.minMax(LINEAR); + assertEquals(1, minMax.getMin()); + assertEquals(8, minMax.getMax()); + + minMax = SeriesUtils.minMax(LINEAR_INVERSE); + assertEquals(1, minMax.getMin()); + assertEquals(8, minMax.getMax()); + + minMax = SeriesUtils.minMax(ZIG_ZAG); + assertEquals(1, minMax.getMin()); + assertEquals(10, minMax.getMax()); + + minMax = SeriesUtils.minMax(NULLS); + assertEquals(-1, minMax.getMin()); + assertEquals(4, minMax.getMax()); + + minMax = SeriesUtils.minMax(SINGLE_VALUE); + assertEquals(3, minMax.getMin()); + assertEquals(3, minMax.getMax()); + + minMax = SeriesUtils.minMax(SINGLE_VALUE_NULL); + assertEquals(null, minMax.getMin()); + assertEquals(null, minMax.getMax()); + + minMax = SeriesUtils.minMax(EMPTY); + assertEquals(null, minMax.getMin()); + assertEquals(null, minMax.getMax()); + } +} 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 45de14c8..1f3df472 100644 --- a/androidplot-core/src/test/java/com/androidplot/xy/XYPlotTest.java +++ b/androidplot-core/src/test/java/com/androidplot/xy/XYPlotTest.java @@ -108,7 +108,7 @@ public void testOriginGrowMode() throws Exception { @Test public void testOriginShrinkMode() throws Exception { plot.addSeries(series1, new LineAndPointFormatter()); - plot.centerOnDomainOrigin(5, null, BoundaryMode.SHRINNK); + plot.centerOnDomainOrigin(5, null, BoundaryMode.SHRINK); plot.calculateMinMaxVals(); assertEquals(0.0, plot.getCalculatedMinX()); @@ -176,7 +176,7 @@ public void testsetDomainBoundaries() throws Exception { // back to big series1.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); - plot.setDomainBoundaries(2, BoundaryMode.SHRINNK, 8, BoundaryMode.SHRINNK); + plot.setDomainBoundaries(2, BoundaryMode.SHRINK, 8, BoundaryMode.SHRINK); plot.calculateMinMaxVals(); // check inital @@ -259,7 +259,7 @@ public void testsetRangeBoundaries() throws Exception { // back to big series1.setModel(numList2,SimpleXYSeries.ArrayFormat.Y_VALS_ONLY); - plot.setRangeBoundaries(2, BoundaryMode.SHRINNK, 8, BoundaryMode.SHRINNK); + plot.setRangeBoundaries(2, BoundaryMode.SHRINK, 8, BoundaryMode.SHRINK); plot.calculateMinMaxVals(); // check inital diff --git a/build.gradle b/build.gradle index 9faf86bf..a68ec16d 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ ext { theBuildToolsVersion = '21.1.2' theCompileSdkVersion = 23 theTargetSdkVersion = 23 - theMinSdkVersion = 4 + theMinSdkVersion = 5 theVersionName = '0.9.6' theVersionCode = 12 } diff --git a/demoapp/src/main/AndroidManifest.xml b/demoapp/src/main/AndroidManifest.xml index 8ea82e53..95a9297e 100644 --- a/demoapp/src/main/AndroidManifest.xml +++ b/demoapp/src/main/AndroidManifest.xml @@ -64,6 +64,12 @@ + + + + + diff --git a/demoapp/src/main/java/com/androidplot/demos/BarPlotExampleActivity.java b/demoapp/src/main/java/com/androidplot/demos/BarPlotExampleActivity.java index 43f5d5ba..4b863a6d 100644 --- a/demoapp/src/main/java/com/androidplot/demos/BarPlotExampleActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/BarPlotExampleActivity.java @@ -312,7 +312,7 @@ private void onPlotClicked(PointF point) { // find the closest value to the selection: - for (SeriesAndFormatter sfPair : plot.getSeriesRegistry()) { + for (SeriesAndFormatter sfPair : plot.getSeriesRegistry().asList()) { XYSeries series = sfPair.getSeries(); for (int i = 0; i < series.size(); i++) { Number thisX = series.getX(i); @@ -323,17 +323,17 @@ private void onPlotClicked(PointF point) { double thisYDistance = LineRegion.measure(y, thisY).doubleValue(); if (selection == null) { - selection = new Pair(i, series); + selection = new Pair<>(i, series); xDistance = thisXDistance; yDistance = thisYDistance; } else if (thisXDistance < xDistance) { - selection = new Pair(i, series); + selection = new Pair<>(i, series); xDistance = thisXDistance; yDistance = thisYDistance; } else if (thisXDistance == xDistance && thisYDistance < yDistance && thisY.doubleValue() >= y.doubleValue()) { - selection = new Pair(i, series); + selection = new Pair<>(i, series); xDistance = thisXDistance; yDistance = thisYDistance; } diff --git a/demoapp/src/main/java/com/androidplot/demos/CandlestickChartActivity.java b/demoapp/src/main/java/com/androidplot/demos/CandlestickChartActivity.java new file mode 100644 index 00000000..e27bdcda --- /dev/null +++ b/demoapp/src/main/java/com/androidplot/demos/CandlestickChartActivity.java @@ -0,0 +1,74 @@ +/* + * Copyright 2016 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.demos; + +import android.app.Activity; +import android.graphics.DashPathEffect; +import android.os.Bundle; +import com.androidplot.candlestick.CandlestickFormatter; +import com.androidplot.candlestick.CandlestickSeries; +import com.androidplot.candlestick.SimpleCandlestickSeries; +import com.androidplot.util.PixelUtils; +import com.androidplot.xy.*; + +import java.util.Arrays; + +/** + * A simple XYPlot + */ +public class CandlestickChartActivity extends Activity +{ + + private XYPlot plot; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.simple_xy_plot_example); + + // initialize our XYPlot reference: + plot = (XYPlot) findViewById(R.id.plot); + + plot.getLayoutManager().moveToBottom(plot.getTitleWidget()); + + // max + Number[] yVals = {10, 15, 8}; + + // min + Number[] zVals = {1, 5, 0}; + + // open + Number[] aVals = {2, 7, 5}; + + // close + Number[] bVals = {5, 6, 7}; + + CandlestickSeries series1 = new SimpleCandlestickSeries(null, + Arrays.asList(yVals), Arrays.asList(zVals), Arrays.asList(aVals), Arrays.asList(bVals), "bla"); + + CandlestickFormatter cf1 = new CandlestickFormatter(); + + plot.addSeries(series1, cf1); + + // reduce the number of range labels + plot.setTicksPerRangeLabel(3); + + // rotate domain labels 45 degrees to make them more compact horizontally: + plot.getGraphWidget().setDomainLabelOrientation(-45); + } +} diff --git a/demoapp/src/main/java/com/androidplot/demos/MainActivity.java b/demoapp/src/main/java/com/androidplot/demos/MainActivity.java index b1ec4aae..b2690300 100644 --- a/demoapp/src/main/java/com/androidplot/demos/MainActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/MainActivity.java @@ -66,6 +66,14 @@ public void onClick(View view) { } }); + Button startCandlestickExButton = (Button) findViewById(R.id.startCandlestickExButton); + startCandlestickExButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + startActivity(new Intent(MainActivity.this, CandlestickChartActivity.class)); + } + }); + Button startSimpleXYExButton = (Button) findViewById(R.id.startSimpleXYExButton); startSimpleXYExButton.setOnClickListener(new View.OnClickListener() { @Override diff --git a/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java b/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java index ee23f655..bfdff8a9 100644 --- a/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java @@ -22,6 +22,11 @@ import com.androidplot.xy.SimpleXYSeries; import com.androidplot.xy.XYSeries; import com.androidplot.xy.*; + +import java.text.FieldPosition; +import java.text.Format; +import java.text.NumberFormat; +import java.text.ParsePosition; import java.util.Arrays; /** diff --git a/demoapp/src/main/res/layout/main.xml b/demoapp/src/main/res/layout/main.xml index a54f8a12..11f53e7e 100644 --- a/demoapp/src/main/res/layout/main.xml +++ b/demoapp/src/main/res/layout/main.xml @@ -20,73 +20,100 @@ android:layout_width="fill_parent" android:layout_height="fill_parent"> - - -