From 9425b5a24981f4ff3294ad61eaaa85f4d86dff8b Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Tue, 8 Dec 2015 10:44:26 -0600 Subject: [PATCH 001/149] * new demo app logo * Removed actionbar from demo app * More styleable attrs, including a couple for Pie. * Refactored the XYGraphWidget to use BoxModel / DisplayDimensions to manage it's grid padding/margin. * Updated the SimpleXYPlot example to be a little prettier and a little more informative --- .../src/main/java/com/androidplot/Plot.java | 9 + .../java/com/androidplot/pie/PieChart.java | 7 +- .../java/com/androidplot/ui/BoxModel.java | 7 +- .../java/com/androidplot/util/RectFUtils.java | 41 ++++ .../com/androidplot/xy/XYGraphWidget.java | 215 +++++++----------- .../main/java/com/androidplot/xy/XYPlot.java | 33 ++- .../src/main/res/values/attrs.xml | 29 +++ demoapp/build.gradle | 2 +- demoapp/src/main/AndroidManifest.xml | 5 +- .../demos/BarPlotExampleActivity.java | 6 +- .../demos/SimpleXYPlotActivity.java | 52 +++-- .../demos/TouchZoomExampleActivity.java | 3 +- .../demos/XYPlotWithBgImgActivity.java | 2 +- .../main/res/drawable-hdpi/ic_launcher.png | Bin 0 -> 3416 bytes .../main/res/drawable-xhdpi/ic_launcher.png | Bin 0 -> 4584 bytes .../main/res/drawable-xxhdpi/ic_launcher.png | Bin 0 -> 7353 bytes .../main/res/drawable-xxxhdpi/ic_launcher.png | Bin 0 -> 9977 bytes demoapp/src/main/res/drawable/ic_launcher.png | Bin 0 -> 2146 bytes demoapp/src/main/res/drawable/icon.png | Bin 8764 -> 0 bytes .../src/main/res/layout/bar_plot_example.xml | 2 +- .../main/res/layout/dynamicxyplot_example.xml | 36 ++- .../res/layout/simple_xy_plot_example.xml | 16 +- .../main/res/layout/touch_zoom_example.xml | 2 +- demoapp/src/main/res/values/strings.xml | 4 +- .../res/xml/demo_app_widget_provider_info.xml | 2 +- ...l => line_point_formatter_with_labels.xml} | 11 +- ...=> line_point_formatter_with_labels_2.xml} | 11 +- 27 files changed, 268 insertions(+), 227 deletions(-) create mode 100644 androidplot-core/src/main/java/com/androidplot/util/RectFUtils.java create mode 100755 demoapp/src/main/res/drawable-hdpi/ic_launcher.png create mode 100755 demoapp/src/main/res/drawable-xhdpi/ic_launcher.png create mode 100755 demoapp/src/main/res/drawable-xxhdpi/ic_launcher.png create mode 100755 demoapp/src/main/res/drawable-xxxhdpi/ic_launcher.png create mode 100755 demoapp/src/main/res/drawable/ic_launcher.png delete mode 100644 demoapp/src/main/res/drawable/icon.png rename demoapp/src/main/res/xml/{line_point_formatter_with_plf1.xml => line_point_formatter_with_labels.xml} (76%) rename demoapp/src/main/res/xml/{line_point_formatter_with_plf2.xml => line_point_formatter_with_labels_2.xml} (76%) diff --git a/androidplot-core/src/main/java/com/androidplot/Plot.java b/androidplot-core/src/main/java/com/androidplot/Plot.java index a2f9baf5..02e93078 100644 --- a/androidplot-core/src/main/java/com/androidplot/Plot.java +++ b/androidplot-core/src/main/java/com/androidplot/Plot.java @@ -375,6 +375,15 @@ public void run() { * @param attrs */ private void processBaseAttrs(TypedArray attrs) { + + // renderMode + RenderMode renderMode = RenderMode.values() + [attrs.getInt(R.styleable.Plot_renderMode, getRenderMode().ordinal())]; + if(renderMode != getRenderMode()) { + setRenderMode(renderMode); + } + + // title setTitle(attrs.getString(R.styleable.Plot_label)); getTitleWidget().getLabelPaint().setTextSize( attrs.getDimension(R.styleable.Plot_labelTextSize, 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 81091b7d..0c542964 100644 --- a/androidplot-core/src/main/java/com/androidplot/pie/PieChart.java +++ b/androidplot-core/src/main/java/com/androidplot/pie/PieChart.java @@ -20,9 +20,11 @@ import android.content.res.TypedArray; import android.util.AttributeSet; import com.androidplot.Plot; +import com.androidplot.R; import com.androidplot.ui.AnchorPosition; import com.androidplot.ui.SizeLayoutType; import com.androidplot.ui.Size; +import com.androidplot.util.AttrUtils; import com.androidplot.util.PixelUtils; import com.androidplot.ui.XLayoutStyle; import com.androidplot.ui.YLayoutStyle; @@ -82,7 +84,10 @@ protected void onPreInit() { @Override protected void processAttrs(TypedArray attrs) { - // TODO + + // borderPaint + AttrUtils.configureLinePaint(attrs, getBorderPaint(), + R.styleable.pie_PieChart_pieBorderColor, R.styleable.pie_PieChart_pieBorderThickness); } public PieWidget getPieWidget() { diff --git a/androidplot-core/src/main/java/com/androidplot/ui/BoxModel.java b/androidplot-core/src/main/java/com/androidplot/ui/BoxModel.java index 15989929..298f565e 100644 --- a/androidplot-core/src/main/java/com/androidplot/ui/BoxModel.java +++ b/androidplot-core/src/main/java/com/androidplot/ui/BoxModel.java @@ -33,11 +33,12 @@ public class BoxModel implements BoxModelable{ private float paddingTop; private float paddingRight; private float paddingBottom; - //private RectF marginRect; - //private RectF paddingRect; + /** + * Default with 0 for all padding / margin values + */ public BoxModel() { - + // nothing to do } @SuppressWarnings("SameParameterValue") diff --git a/androidplot-core/src/main/java/com/androidplot/util/RectFUtils.java b/androidplot-core/src/main/java/com/androidplot/util/RectFUtils.java new file mode 100644 index 00000000..016f2f32 --- /dev/null +++ b/androidplot-core/src/main/java/com/androidplot/util/RectFUtils.java @@ -0,0 +1,41 @@ +/* + * 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.RectF; + +/** + * Convenience methods for dealing with {@link android.graphics.RectF} + */ +public abstract class RectFUtils { + + + /** + * Determine if two {@link RectF} instances are equal. Must be used in place + * of default equality operation due to a bug that exists in older versions of Android: + * http://stackoverflow.com/questions/13517852/rectf-equals-fails-on-android-versions-below-jelly-bean + * @param r1 May not be null + * @param r2 May not be null + * @return True if r1 and r2 are identical, false otherwise. + */ + public static boolean areIdentical(RectF r1, RectF r2) { + return r1.left == r2.left && + r1.top == r2.top && + r1.right == r2.right && + r1.bottom == r2.bottom; + } +} 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 bf0cf1aa..53b91153 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java @@ -19,6 +19,7 @@ import android.graphics.*; import com.androidplot.exception.PlotRenderException; +import com.androidplot.ui.BoxModel; import com.androidplot.ui.LayoutManager; import com.androidplot.ui.RenderStack; import com.androidplot.ui.Size; @@ -118,6 +119,14 @@ public void setRangeLabelPaintMap(Mapping rangeLabelPaintMap) { setRangeLabelTickPaintMap(rangeLabelPaintMap); } + public BoxModel getGridBox() { + return gridBox; + } + + public void setGridBox(BoxModel gridBox) { + this.gridBox = gridBox; + } + /** * Will be used in a future version. */ @@ -146,10 +155,10 @@ public enum XYPlotOrientation { private int ticksPerRangeLabel = 1; private int ticksPerDomainLabel = 1; - private float gridPaddingTop = 0; - private float gridPaddingBottom = 0; - private float gridPaddingLeft = 0; - private float gridPaddingRight = 0; + + private BoxModel gridBox = new BoxModel(); + private DisplayDimensions gridDimensions; + private int domainTickExtension = 5; private int rangeTickExtension = 5; private int domainLabelSubTickExtension = 0; @@ -172,8 +181,7 @@ public enum XYPlotOrientation { private Paint rangeOriginLinePaint; private Paint domainOriginTickLabelPaint; private Paint rangeOriginTickLabelPaint; - private RectF gridRect; - private RectF paddedGridRect; + private float domainCursorPosition; private float rangeCursorPosition; @SuppressWarnings("FieldCanBeLocal") @@ -357,43 +365,6 @@ public AxisValueLabelFormatter getAxisValueLabelFormatterForRangeVal( return null; } - /** - * Returns the formatter associated with the first (bottom-most) Region - * containing value. - * - * @param value - * @return - */ - /* - * public AxisValueLabelFormatter getXYAxisFormatterForRangeVal(double - * value) { return getRegionContainingVal(rangeLabelRegions, value); } - *//** - * Returns the formatter associated with the first (bottom-most) Region - * containing value. - * - * @param value - * @return - */ - /* - * public AxisValueLabelFormatter getXYAxisFormatterForDomainVal(double - * value) { return getRegionContainingVal(domainLabelRegions, value); } - */ - - /* - * private AxisValueLabelFormatter getRegionContainingVal(ZHash zhash, double val) { for (LineRegion r : - * zhash.elements()) { if (r.contains(val)) { return - * rangeLabelRegions.get(r); } } // nothing found return null; } - */ - - /** - * Returns a RectF representing the grid area last drawn by this plot. - * - * @return - */ - public RectF getGridRect() { - return paddedGridRect; - } private String getFormattedRangeValue(Number value) { return rangeValueFormat.format(value); } @@ -423,9 +394,9 @@ public Double getYVal(float yPix) { || plot.getCalculatedMaxY() == null) { return null; } - return ValPixConverter.pixToVal(yPix - paddedGridRect.top, plot + return ValPixConverter.pixToVal(yPix - gridDimensions.paddedRect.top, plot .getCalculatedMinY().doubleValue(), plot.getCalculatedMaxY() - .doubleValue(), paddedGridRect.height(), true); + .doubleValue(), gridDimensions.paddedRect.height(), true); } /** @@ -449,23 +420,19 @@ public Double getXVal(float xPix) { || plot.getCalculatedMaxX() == null) { return null; } - return ValPixConverter.pixToVal(xPix - paddedGridRect.left, plot + return ValPixConverter.pixToVal(xPix - gridDimensions.paddedRect.left, plot .getCalculatedMinX().doubleValue(), plot.getCalculatedMaxX() - .doubleValue(), paddedGridRect.width(), false); + .doubleValue(), gridDimensions.paddedRect.width(), false); } @Override protected void doOnDraw(Canvas canvas, RectF widgetRect) throws PlotRenderException { - gridRect = getGridRect(widgetRect); // used for drawing the background - // of the grid - paddedGridRect = getPaddedGridRect(gridRect); // used for drawing lines - // etc. - //Log.v(TAG, "gridRect :" + gridRect); - //Log.v(TAG, "paddedGridRect :" + paddedGridRect); - // if (!plot.isEmpty()) { + + calculateGridDimensions(widgetRect); + // don't draw if we have no space to draw into - if ((paddedGridRect.height() > 0.0f) && (paddedGridRect.width() > 0.0f)) { + if ((gridDimensions.paddedRect.height() > 0.0f) && (gridDimensions.paddedRect.width() > 0.0f)) { if (plot.getCalculatedMinX() != null && plot.getCalculatedMaxX() != null && plot.getCalculatedMinY() != null @@ -478,20 +445,20 @@ protected void doOnDraw(Canvas canvas, RectF widgetRect) } } } - // } } - private RectF getGridRect(RectF widgetRect) { - return new RectF(widgetRect.left + ((rangeAxisLeft)?rangeTickLabelWidth:1), + private void calculateGridDimensions(RectF widgetRect) { + RectF r = new RectF(widgetRect.left + ((rangeAxisLeft)?rangeTickLabelWidth:1), widgetRect.top + ((domainAxisBottom)?1:domainTickLabelWidth), widgetRect.right - ((rangeAxisLeft)?1:rangeTickLabelWidth), widgetRect.bottom - ((domainAxisBottom)?domainTickLabelWidth:1)); - } - private RectF getPaddedGridRect(RectF gridRect) { - return new RectF(gridRect.left + gridPaddingLeft, gridRect.top - + gridPaddingTop, gridRect.right - gridPaddingRight, - gridRect.bottom - gridPaddingBottom); + // don't calculate if nothing has changed: + if(gridDimensions == null || !RectFUtils.areIdentical(r, gridDimensions.canvasRect)) { + RectF mRect = gridBox.getMarginatedRect(r); + RectF pRect = gridBox.getPaddedRect(mRect); + gridDimensions = new DisplayDimensions(r, mRect, pRect); + } } private void drawTickText(Canvas canvas, XYAxisType axis, Number value, @@ -533,6 +500,8 @@ private void drawTickText(Canvas canvas, XYAxisType axis, Number value, private void drawDomainTick(Canvas canvas, float xPix, Number xVal, Paint labelPaint, Paint linePaint, boolean drawLineOnly) { + + final RectF gridRect = gridDimensions.paddedRect; if (!drawLineOnly) { if (linePaint != null && (domainTick || domainTickExtension > 0)) { if (domainAxisBottom){ @@ -570,6 +539,7 @@ private void drawDomainTick(Canvas canvas, float xPix, Number xVal, public void drawRangeTick(Canvas canvas, float yPix, Number yVal, Paint labelPaint, Paint linePaint, boolean drawLineOnly) { + final RectF gridRect = gridDimensions.paddedRect; if (!drawLineOnly) { if (linePaint != null && (rangeTick || rangeTickExtension > 0)) { if (rangeAxisLeft){ @@ -609,9 +579,9 @@ public void drawRangeTick(Canvas canvas, float yPix, Number yVal, * @param canvas */ protected void drawGrid(Canvas canvas) { - + final RectF paddedGridRect = gridDimensions.paddedRect; if (gridBackgroundPaint != null) { - canvas.drawRect(gridRect, gridBackgroundPaint); + canvas.drawRect(gridDimensions.paddedRect, gridBackgroundPaint); } float domainOriginF; @@ -804,18 +774,19 @@ protected void drawGrid(Canvas canvas) { */ private void drawMarkerText(Canvas canvas, String text, ValueMarker marker, float x, float y) { + final RectF paddedRect = gridDimensions.paddedRect; x += MARKER_LABEL_SPACING; y -= MARKER_LABEL_SPACING; RectF textRect = new RectF(FontUtils.getStringDimensions(text, marker.getTextPaint())); textRect.offsetTo(x, y - textRect.height()); - if (textRect.right > paddedGridRect.right) { - textRect.offset(-(textRect.right - paddedGridRect.right), 0); + if (textRect.right > paddedRect.right) { + textRect.offset(-(textRect.right - paddedRect.right), 0); } - if (textRect.top < paddedGridRect.top) { - textRect.offset(0, paddedGridRect.top - textRect.top); + if (textRect.top < paddedRect.top) { + textRect.offset(0, paddedRect.top - textRect.top); } canvas.drawText(text, textRect.left, textRect.bottom, @@ -826,20 +797,21 @@ private void drawMarkerText(Canvas canvas, String text, ValueMarker marker, protected void drawMarkers(Canvas canvas) { for (YValueMarker marker : plot.getYValueMarkers()) { + final RectF paddedRect = gridDimensions.paddedRect; + if (marker.getValue() != null) { double yVal = marker.getValue().doubleValue(); float yPix = ValPixConverter.valToPix(yVal, plot .getCalculatedMinY().doubleValue(), plot - .getCalculatedMaxY().doubleValue(), paddedGridRect + .getCalculatedMaxY().doubleValue(), paddedRect .height(), true); - yPix += paddedGridRect.top; - canvas.drawLine(paddedGridRect.left, yPix, - paddedGridRect.right, yPix, marker.getLinePaint()); + yPix += paddedRect.top; + canvas.drawLine(paddedRect.left, yPix, + paddedRect.right, yPix, marker.getLinePaint()); - // String text = getFormattedRangeValue(yVal); float xPix = marker.getTextPosition().getPixelValue( - paddedGridRect.width()); - xPix += paddedGridRect.left; + paddedRect.width()); + xPix += paddedRect.left; if (marker.getText() != null) { drawMarkerText(canvas, marker.getText(), marker, xPix, yPix); @@ -852,20 +824,22 @@ protected void drawMarkers(Canvas canvas) { } for (XValueMarker marker : plot.getXValueMarkers()) { + + final RectF paddedRect = gridDimensions.paddedRect; + if (marker.getValue() != null) { double xVal = marker.getValue().doubleValue(); float xPix = ValPixConverter.valToPix(xVal, plot .getCalculatedMinX().doubleValue(), plot - .getCalculatedMaxX().doubleValue(), paddedGridRect + .getCalculatedMaxX().doubleValue(), paddedRect .width(), false); - xPix += paddedGridRect.left; - canvas.drawLine(xPix, paddedGridRect.top, xPix, - paddedGridRect.bottom, marker.getLinePaint()); + xPix += paddedRect.left; + canvas.drawLine(xPix, paddedRect.top, xPix, + paddedRect.bottom, marker.getLinePaint()); - // String text = getFormattedDomainValue(xVal); float yPix = marker.getTextPosition().getPixelValue( - paddedGridRect.height()); - yPix += paddedGridRect.top; + paddedRect.height()); + yPix += paddedRect.top; if (marker.getText() != null) { drawMarkerText(canvas, marker.getText(), marker, xPix, yPix); } else { @@ -878,25 +852,27 @@ protected void drawMarkers(Canvas canvas) { } protected void drawCursors(Canvas canvas) { + + final RectF paddedRect = gridDimensions.paddedRect; boolean hasDomainCursor = false; // draw the domain cursor: if (domainCursorPaint != null - && domainCursorPosition <= paddedGridRect.right - && domainCursorPosition >= paddedGridRect.left) { + && domainCursorPosition <= paddedRect.right + && domainCursorPosition >= paddedRect.left) { hasDomainCursor = true; - canvas.drawLine(domainCursorPosition, paddedGridRect.top, - domainCursorPosition, paddedGridRect.bottom, + canvas.drawLine(domainCursorPosition, paddedRect.top, + domainCursorPosition, paddedRect.bottom, domainCursorPaint); } boolean hasRangeCursor = false; // draw the range cursor: if (rangeCursorPaint != null - && rangeCursorPosition >= paddedGridRect.top - && rangeCursorPosition <= paddedGridRect.bottom) { + && rangeCursorPosition >= paddedRect.top + && rangeCursorPosition <= paddedRect.bottom) { hasRangeCursor = true; - canvas.drawLine(paddedGridRect.left, rangeCursorPosition, - paddedGridRect.right, rangeCursorPosition, rangeCursorPaint); + canvas.drawLine(paddedRect.left, rangeCursorPosition, + paddedRect.right, rangeCursorPosition, rangeCursorPaint); } if (drawCursorLabelEnabled && cursorLabelPaint != null @@ -913,17 +889,15 @@ protected void drawCursors(Canvas canvas) { - cursorRect.height()); // if we are too close to the right edge of the plot, we will move - // the - // label to the left side of our cursor: - if (cursorRect.right >= paddedGridRect.right) { + // the label to the left side of our cursor: + if (cursorRect.right >= paddedRect.right) { cursorRect.offsetTo(domainCursorPosition - cursorRect.width(), cursorRect.top); } // same thing for the top edge of the plot: - // dunno why but these rects can have negative values for top and - // bottom. - if (cursorRect.top <= paddedGridRect.top) { + // dunno why but these rects can have negative values for top and bottom. + if (cursorRect.top <= paddedRect.top) { cursorRect.offsetTo(cursorRect.left, rangeCursorPosition); } @@ -945,13 +919,13 @@ protected void drawCursors(Canvas canvas) { protected void drawData(Canvas canvas) throws PlotRenderException { try { canvas.save(Canvas.ALL_SAVE_FLAG); - canvas.clipRect(gridRect, android.graphics.Region.Op.INTERSECT); + canvas.clipRect(gridDimensions.marginatedRect, android.graphics.Region.Op.INTERSECT); renderStack.sync(); for(RenderStack.StackElement thisElement : renderStack.getElements()) { if(thisElement.isEnabled()) { plot.getRenderer(thisElement.get().getFormatter().getRendererClass()). - render(canvas, paddedGridRect, thisElement.get(), renderStack); + render(canvas, gridDimensions.paddedRect, thisElement.get(), renderStack); } } @@ -1360,45 +1334,6 @@ public void setTicksPerDomainLabel(int ticksPerDomainLabel) { this.ticksPerDomainLabel = ticksPerDomainLabel; } - public void setGridPaddingTop(float gridPaddingTop) { - this.gridPaddingTop = gridPaddingTop; - } - - public float getGridPaddingBottom() { - return gridPaddingBottom; - } - - public void setGridPaddingBottom(float gridPaddingBottom) { - this.gridPaddingBottom = gridPaddingBottom; - } - - public float getGridPaddingLeft() { - return gridPaddingLeft; - } - - public void setGridPaddingLeft(float gridPaddingLeft) { - this.gridPaddingLeft = gridPaddingLeft; - } - - public float getGridPaddingRight() { - return gridPaddingRight; - } - - public void setGridPaddingRight(float gridPaddingRight) { - this.gridPaddingRight = gridPaddingRight; - } - - public float getGridPaddingTop() { - return gridPaddingTop; - } - - public void setGridPadding(float left, float top, float right, float bottom) { - setGridPaddingLeft(left); - setGridPaddingTop(top); - setGridPaddingRight(right); - setGridPaddingBottom(bottom); - } - public Paint getDomainOriginLinePaint() { return domainOriginLinePaint; } @@ -1659,4 +1594,8 @@ public void setDomainAxisPosition(boolean domainAxisBottom, boolean domainAxisOv } } } + + public DisplayDimensions getGridDimensions() { + return gridDimensions; + } } 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 43d6d4df..db6883ce 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java @@ -289,13 +289,20 @@ protected void processAttrs(TypedArray attrs) { AttrUtils.configureTextPaint(attrs, getRangeLabelWidget().getLabelPaint(), R.styleable.xy_XYPlot_rangeLabelTextColor, R.styleable.xy_XYPlot_rangeLabelTextSize); - // graphBox + // graphWidget AttrUtils.configureBoxModelable(attrs, getGraphWidget(), R.styleable.xy_XYPlot_graphMarginTop, R.styleable.xy_XYPlot_graphMarginBottom, R.styleable.xy_XYPlot_graphMarginLeft, R.styleable.xy_XYPlot_graphMarginRight, R.styleable.xy_XYPlot_graphPaddingTop, R.styleable.xy_XYPlot_graphPaddingBottom, R.styleable.xy_XYPlot_graphPaddingLeft, R.styleable.xy_XYPlot_graphPaddingRight); + // gridRect + AttrUtils.configureBoxModelable(attrs, getGraphWidget().getGridBox(), + R.styleable.xy_XYPlot_gridMarginTop, R.styleable.xy_XYPlot_gridMarginBottom, + R.styleable.xy_XYPlot_gridMarginLeft, R.styleable.xy_XYPlot_gridMarginRight, + R.styleable.xy_XYPlot_gridPaddingTop, R.styleable.xy_XYPlot_gridPaddingBottom, + R.styleable.xy_XYPlot_gridPaddingLeft, R.styleable.xy_XYPlot_gridPaddingRight); + // domainTickLabelPaint AttrUtils.configureTextPaint(attrs, getGraphWidget().getDomainTickLabelPaint(), R.styleable.xy_XYPlot_domainTickLabelTextColor, @@ -340,29 +347,21 @@ protected void processAttrs(TypedArray attrs) { R.styleable.xy_XYPlot_legendAnchorPosition, R.styleable.xy_XYPlot_legendVisible); - // while it's generally bad practice to retrieve an enum via ordinal value like this, i can think - // of no real-world scenario that would lead to a problem in this specific case: - //getLegendWidget().setAnchor(AnchorPosition.values()[attrs.getInt( - // R.styleable.xy_XYPlot_legendAnchorPosition, getLegendWidget().getAnchor().ordinal())]); + AttrUtils.configureLinePaint(attrs, getGraphWidget().getDomainGridLinePaint(), + R.styleable.xy_XYPlot_graphDomainLineColor, R.styleable.xy_XYPlot_graphDomainLineThickness); + + AttrUtils.configureLinePaint(attrs, getGraphWidget().getRangeGridLinePaint(), + R.styleable.xy_XYPlot_graphRangeLineColor, R.styleable.xy_XYPlot_graphRangeLineThickness); getGraphWidget().getBackgroundPaint().setColor(attrs.getColor( R.styleable.xy_XYPlot_graphBackgroundColor, getGraphWidget().getBackgroundPaint().getColor())); - getGraphWidget().getDomainGridLinePaint().setColor(attrs.getColor( - R.styleable.xy_XYPlot_graphDomainLineColor, getGraphWidget().getDomainGridLinePaint().getColor())); - - getGraphWidget().getRangeGridLinePaint().setColor(attrs.getColor( - R.styleable.xy_XYPlot_graphRangeLineColor, getGraphWidget().getRangeGridLinePaint().getColor())); - getGraphWidget().getGridBackgroundPaint().setColor(attrs.getColor( R.styleable.xy_XYPlot_gridBackgroundColor, getGraphWidget().getGridBackgroundPaint().getColor())); } public void setGridPadding(float left, float top, float right, float bottom) { - getGraphWidget().setGridPaddingTop(top); - getGraphWidget().setGridPaddingBottom(bottom); - getGraphWidget().setGridPaddingLeft(left); - getGraphWidget().setGridPaddingRight(right); + getGraphWidget().getGridBox().setPadding(left, top, right, bottom); } @Override @@ -383,8 +382,8 @@ protected void notifyListenersBeforeDraw(Canvas canvas) { * @return */ public boolean containsPoint(float x, float y) { - if (getGraphWidget().getGridRect() != null) { - return getGraphWidget().getGridRect().contains(x, y); + if (getGraphWidget().getGridDimensions().marginatedRect != null) { + return getGraphWidget().getGridDimensions().marginatedRect.contains(x, y); } return false; } diff --git a/androidplot-core/src/main/res/values/attrs.xml b/androidplot-core/src/main/res/values/attrs.xml index 70616cef..ac5bc983 100644 --- a/androidplot-core/src/main/res/values/attrs.xml +++ b/androidplot-core/src/main/res/values/attrs.xml @@ -17,6 +17,11 @@ + + + + + @@ -73,7 +78,9 @@ + + @@ -82,6 +89,7 @@ + @@ -89,6 +97,8 @@ + + @@ -97,7 +107,20 @@ + + + + + + + + + + + + + @@ -126,4 +149,10 @@ + + + + + + \ No newline at end of file diff --git a/demoapp/build.gradle b/demoapp/build.gradle index f801ea7b..8eed462d 100644 --- a/demoapp/build.gradle +++ b/demoapp/build.gradle @@ -29,7 +29,7 @@ android { defaultConfig { versionCode theVersionCode versionName theVersionName - minSdkVersion theMinSdkVersion + minSdkVersion 11 targetSdkVersion theTargetSdkVersion applicationId "com.androidplot.demos" } diff --git a/demoapp/src/main/AndroidManifest.xml b/demoapp/src/main/AndroidManifest.xml index fbb8a162..ca9f41c6 100644 --- a/demoapp/src/main/AndroidManifest.xml +++ b/demoapp/src/main/AndroidManifest.xml @@ -24,7 +24,8 @@ + android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen" + android:icon="@drawable/ic_launcher"> @@ -88,7 +89,7 @@ diff --git a/demoapp/src/main/java/com/androidplot/demos/BarPlotExampleActivity.java b/demoapp/src/main/java/com/androidplot/demos/BarPlotExampleActivity.java index a5572c38..43f5d5ba 100644 --- a/demoapp/src/main/java/com/androidplot/demos/BarPlotExampleActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/BarPlotExampleActivity.java @@ -94,7 +94,7 @@ public void onCreate(Bundle savedInstanceState) setContentView(R.layout.bar_plot_example); // initialize our XYPlot reference: - plot = (XYPlot) findViewById(R.id.mySimpleXYPlot); + plot = (XYPlot) findViewById(R.id.plot); formatter1 = new MyBarFormatter(Color.argb(200, 100, 150, 100), Color.LTGRAY); formatter2 = new MyBarFormatter(Color.argb(200, 100, 100, 150), Color.LTGRAY); @@ -123,7 +123,7 @@ public void onCreate(Bundle savedInstanceState) // reduce the number of range labels plot.setTicksPerRangeLabel(3); plot.setRangeLowerBoundary(0, BoundaryMode.FIXED); - plot.getGraphWidget().setGridPadding(30, 10, 30, 0); + plot.getGraphWidget().getGridBox().setPadding(30, 10, 30, 0); plot.setTicksPerDomainLabel(2); @@ -301,7 +301,7 @@ private void onPlotClicked(PointF point) { // make sure the point lies within the graph area. we use gridrect // because it accounts for margins and padding as well. - if (plot.getGraphWidget().getGridRect().contains(point.x, point.y)) { + if (plot.getGraphWidget().getGridDimensions().marginatedRect.contains(point.x, point.y)) { Number x = plot.getXVal(point); Number y = plot.getYVal(point); diff --git a/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java b/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java index d1adb43d..6b800c4e 100644 --- a/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java @@ -16,7 +16,9 @@ package com.androidplot.demos; import android.app.Activity; +import android.graphics.DashPathEffect; import android.os.Bundle; +import com.androidplot.util.PixelUtils; import com.androidplot.xy.SimpleXYSeries; import com.androidplot.xy.XYSeries; import com.androidplot.xy.*; @@ -37,42 +39,56 @@ public void onCreate(Bundle savedInstanceState) setContentView(R.layout.simple_xy_plot_example); // initialize our XYPlot reference: - plot = (XYPlot) findViewById(R.id.mySimpleXYPlot); + plot = (XYPlot) findViewById(R.id.plot); - // Create a couple arrays of y-values to plot: - Number[] series1Numbers = {1, 8, 5, 2, 7, 4}; - Number[] series2Numbers = {4, 6, 3, 8, 2, 10}; + // create a couple arrays of y-values to plot: + Number[] series1Numbers = {1, 4, 2, 8, 4, 16, 8, 32, 16, 64}; + Number[] series2Numbers = {5, 2, 10, 5, 20, 10, 40, 20, 80, 40}; - // Turn the above arrays into XYSeries': + // turn the above arrays into XYSeries': // (Y_VALS_ONLY means use the element index as the x value) - XYSeries series1 = new SimpleXYSeries( - Arrays.asList(series1Numbers), - SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, - "Series1"); + XYSeries series1 = new SimpleXYSeries(Arrays.asList(series1Numbers), + SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Series1"); - // same as above XYSeries series2 = new SimpleXYSeries(Arrays.asList(series2Numbers), SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Series2"); - // Create a getFormatter to use for drawing a series using LineAndPointRenderer - // and configure it from xml: + // create formatters to use for drawing a series using LineAndPointRenderer + // and configure them from xml: LineAndPointFormatter series1Format = new LineAndPointFormatter(); series1Format.setPointLabelFormatter(new PointLabelFormatter()); series1Format.configure(getApplicationContext(), - R.xml.line_point_formatter_with_plf1); + R.xml.line_point_formatter_with_labels); - // add a new series' to the xyplot: - plot.addSeries(series1, series1Format); - - // same as above: LineAndPointFormatter series2Format = new LineAndPointFormatter(); series2Format.setPointLabelFormatter(new PointLabelFormatter()); series2Format.configure(getApplicationContext(), - R.xml.line_point_formatter_with_plf2); + R.xml.line_point_formatter_with_labels_2); + + // add an "dash" effect to the series2 line: + series2Format.getLinePaint().setPathEffect( + new DashPathEffect(new float[] { + + // always use DP when specifying pixel sizes, to keep things consistent across devices: + PixelUtils.dpToPix(20), + PixelUtils.dpToPix(15)}, 0)); + + // just for fun, add some smoothing to the lines: + // see: http://androidplot.com/smooth-curves-and-androidplot/ + series1Format.setInterpolationParams( + new CatmullRomInterpolator.Params(10, CatmullRomInterpolator.Type.Centripetal)); + + series2Format.setInterpolationParams( + new CatmullRomInterpolator.Params(10, CatmullRomInterpolator.Type.Centripetal)); + + // add a new series' to the xyplot: + plot.addSeries(series1, series1Format); plot.addSeries(series2, series2Format); // 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/TouchZoomExampleActivity.java b/demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java index b5f796e8..3df2f728 100644 --- a/demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java @@ -21,7 +21,6 @@ import android.app.Activity; import android.graphics.Color; -import android.graphics.Paint; import android.graphics.PointF; import android.os.Bundle; import android.view.MotionEvent; @@ -63,7 +62,7 @@ public void onClick(View view) { mySimpleXYPlot.redraw(); } }); - mySimpleXYPlot = (XYPlot) findViewById(R.id.mySimpleXYPlot); + mySimpleXYPlot = (XYPlot) findViewById(R.id.plot); mySimpleXYPlot.setOnTouchListener(this); mySimpleXYPlot.getGraphWidget().setTicksPerRangeLabel(2); mySimpleXYPlot.getGraphWidget().setTicksPerDomainLabel(2); diff --git a/demoapp/src/main/java/com/androidplot/demos/XYPlotWithBgImgActivity.java b/demoapp/src/main/java/com/androidplot/demos/XYPlotWithBgImgActivity.java index 1e4c9fc1..503cb372 100644 --- a/demoapp/src/main/java/com/androidplot/demos/XYPlotWithBgImgActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/XYPlotWithBgImgActivity.java @@ -121,7 +121,7 @@ public void onGraphStyleToggle(View v) { ); plot.getGraphWidget().getGridBackgroundPaint().setShader(lg);*/ - RectF rect = plot.getGraphWidget().getGridRect(); + RectF rect = plot.getGraphWidget().getGridDimensions().marginatedRect; BitmapShader myShader = new BitmapShader( Bitmap.createScaledBitmap( BitmapFactory.decodeResource( diff --git a/demoapp/src/main/res/drawable-hdpi/ic_launcher.png b/demoapp/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..4036921570db566bea90db5a7fb9b9612ba1ee8f GIT binary patch literal 3416 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 literal 0 HcmV?d00001 diff --git a/demoapp/src/main/res/drawable-xxxhdpi/ic_launcher.png b/demoapp/src/main/res/drawable-xxxhdpi/ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..92187631239b16d097592e3e09de7f50eac138b5 GIT binary patch literal 9977 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 P(gx5_(N(Tiv<>?|K(&D- literal 0 HcmV?d00001 diff --git a/demoapp/src/main/res/drawable/ic_launcher.png b/demoapp/src/main/res/drawable/ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..56d3977d087920945d3a53faa89591edcafa61b7 GIT binary patch literal 2146 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 diff --git a/demoapp/src/main/res/layout/bar_plot_example.xml b/demoapp/src/main/res/layout/bar_plot_example.xml index 05998d26..77ef4a72 100644 --- a/demoapp/src/main/res/layout/bar_plot_example.xml +++ b/demoapp/src/main/res/layout/bar_plot_example.xml @@ -21,7 +21,7 @@ android:orientation="vertical"> + style="@style/simple_xy" + android:id="@+id/dynamicXYPlot" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + androidplot.renderMode="use_background_thread" + ap:label="A Dynamic XY Plot" + ap:domainLabel="Domain" + ap:rangeLabel="Range" + ap:legendTextSize="15sp" + ap:legendIconHeight="15dp" + ap:legendIconWidth="15dp" + ap:legendHeight="25dp" + ap:legendAnchorPosition="right_bottom"/> diff --git a/demoapp/src/main/res/layout/simple_xy_plot_example.xml b/demoapp/src/main/res/layout/simple_xy_plot_example.xml index 65796f7e..05ef824e 100644 --- a/demoapp/src/main/res/layout/simple_xy_plot_example.xml +++ b/demoapp/src/main/res/layout/simple_xy_plot_example.xml @@ -21,19 +21,31 @@ android:layout_width="match_parent"> - AndroidPlot Demo - + Androidplot Demos A Simple XY Plot - Time Series Yearly UFO Sightings diff --git a/demoapp/src/main/res/xml/demo_app_widget_provider_info.xml b/demoapp/src/main/res/xml/demo_app_widget_provider_info.xml index cebf60ec..753f79f9 100644 --- a/demoapp/src/main/res/xml/demo_app_widget_provider_info.xml +++ b/demoapp/src/main/res/xml/demo_app_widget_provider_info.xml @@ -20,7 +20,7 @@ android:minWidth="294dp" android:minHeight="72dp" android:updatePeriodMillis="86400000" - android:previewImage="@drawable/icon" + android:previewImage="@drawable/ic_launcher" android:initialLayout="@layout/demo_app_widget" android:resizeMode="horizontal|vertical"> \ No newline at end of file diff --git a/demoapp/src/main/res/xml/line_point_formatter_with_plf1.xml b/demoapp/src/main/res/xml/line_point_formatter_with_labels.xml similarity index 76% rename from demoapp/src/main/res/xml/line_point_formatter_with_plf1.xml rename to demoapp/src/main/res/xml/line_point_formatter_with_labels.xml index 042b700b..2cf6008e 100644 --- a/demoapp/src/main/res/xml/line_point_formatter_with_plf1.xml +++ b/demoapp/src/main/res/xml/line_point_formatter_with_labels.xml @@ -16,8 +16,9 @@ --> \ No newline at end of file + linePaint.strokeWidth="5dp" + linePaint.color="#00AA00" + vertexPaint.color="#007700" + vertexPaint.strokeWidth="20dp" + fillPaint.color="#00000000" + pointLabelFormatter.textPaint.color="#FFFFFF"/> \ No newline at end of file diff --git a/demoapp/src/main/res/xml/line_point_formatter_with_plf2.xml b/demoapp/src/main/res/xml/line_point_formatter_with_labels_2.xml similarity index 76% rename from demoapp/src/main/res/xml/line_point_formatter_with_plf2.xml rename to demoapp/src/main/res/xml/line_point_formatter_with_labels_2.xml index e904a0d0..a3851af1 100644 --- a/demoapp/src/main/res/xml/line_point_formatter_with_plf2.xml +++ b/demoapp/src/main/res/xml/line_point_formatter_with_labels_2.xml @@ -16,8 +16,9 @@ --> \ No newline at end of file + linePaint.strokeWidth="5dp" + linePaint.color="#0000AA" + vertexPaint.strokeWidth="20dp" + vertexPaint.color="#000099" + fillPaint.color="#00000000" + pointLabelFormatter.textPaint.color="#FFFFFF"/> \ No newline at end of file From 46acf685cf9e1fbdf42acdeda2b0fc2de31c6931 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Tue, 8 Dec 2015 10:46:21 -0600 Subject: [PATCH 002/149] * Uprev to 0.9.4 / 10 --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 2f7e0c4b..b8118f67 100644 --- a/build.gradle +++ b/build.gradle @@ -10,8 +10,8 @@ ext { theCompileSdkVersion = 23 theTargetSdkVersion = 23 theMinSdkVersion = 4 - theVersionName = '0.9.3' - theVersionCode = 9 + theVersionName = '0.9.4' + theVersionCode = 10 } buildscript { From 697303d2fa1987c083c9f5598c2fbba1c9cd5013 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Tue, 8 Dec 2015 12:12:50 -0600 Subject: [PATCH 003/149] Readme update / simple xy plot example cleanup --- README.md | 2 +- .../main/java/com/androidplot/demos/SimpleXYPlotActivity.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index affa35d7..ad6732ce 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A library for creating dynamic and static charts in Android apps. It’s designe ##### Bitbucket Repo: (Primary) https://bitbucket.org/androidplot/androidplot ##### Github Repo: https://github.com/halfhp/androidplot -![image](http://androidplot.com/wp-content/gallery/plot-examples/device-2013-07-04-110827_0.png) +![image](http://androidplot.com/wp-content/uploads/2012/11/simplexyscreen.png) ## Demo App [Play Store Listing](https://play.google.com/store/apps/details?id=com.androidplot.demos&hl=en) diff --git a/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java b/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java index 6b800c4e..1c3e18c4 100644 --- a/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java @@ -25,7 +25,7 @@ import java.util.Arrays; /** - * A straightforward example of using AndroidPlot to plot some data. + * A simple XYPlot */ public class SimpleXYPlotActivity extends Activity { From a30c2aa67d3bc3945590216ab4be1fcd2fb08039 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Tue, 8 Dec 2015 12:14:51 -0600 Subject: [PATCH 004/149] updated gradle include for 0.9.4 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ad6732ce..b772e7a1 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ To use the library in your gradle project add the following to your `build.gradl ```groovy dependencies { - compile 'com.androidplot:androidplot-core:0.9.3' + compile 'com.androidplot:androidplot-core:0.9.4' } ``` From 26bbcfccb8ad0ba05f45f2d59a7e7ae40957b579 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Tue, 8 Dec 2015 14:48:50 -0600 Subject: [PATCH 005/149] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b772e7a1..51323294 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Androidplot -A library for creating dynamic and static charts in Android apps. It’s designed from the ground up for Android, compatible with all versions of Android from 1.6 onward and is used by over [500 apps](http://www.appbrain.com/stats/libraries/details/androidplot/androidplot) on Google Play. +A library for creating dynamic and static charts in Android apps. It’s designed from the ground up for Android, 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. ##### Website http://androidplot.com ##### Bitbucket Repo: (Primary) https://bitbucket.org/androidplot/androidplot From 1898f8006fe6502306b271521044ba38fbc57c92 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Tue, 8 Dec 2015 17:43:13 -0600 Subject: [PATCH 006/149] Update README.md --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 51323294..deb92731 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ # Androidplot A library for creating dynamic and static charts in Android apps. It’s designed from the ground up for Android, 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. +**Supports:** +* Line Charts +* Scatter Charts +* Bar Charts +* Pie Charts +* Step Charts + ##### Website http://androidplot.com ##### Bitbucket Repo: (Primary) https://bitbucket.org/androidplot/androidplot ##### Github Repo: https://github.com/halfhp/androidplot From 40d95e70739005f11bb8679cae1f967d3a57a25c Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Mon, 14 Dec 2015 09:46:50 -0600 Subject: [PATCH 007/149] * New wearable demo app (work in progress) * Added more styleable attrs for XYPlot's domain and range label widgets * Added getWidth() and getHeight() methods to RectRegion * Added an XY scatter plot example to the demo app * Uprev to 0.9.5 --- .../java/com/androidplot/util/AttrUtils.java | 24 +--- .../java/com/androidplot/xy/RectRegion.java | 28 +++- .../main/java/com/androidplot/xy/XYPlot.java | 37 ++++-- .../src/main/res/values/attrs.xml | 120 +++++++++++++++++- build.gradle | 4 +- demoapp-wearable/.gitignore | 1 + demoapp-wearable/build.gradle | 59 +++++++++ demoapp-wearable/proguard-rules.pro | 17 +++ demoapp-wearable/src/main/AndroidManifest.xml | 39 ++++++ .../demo/wearable/MainActivity.java | 92 ++++++++++++++ .../src/main/res/layout/activity_main.xml | 70 ++++++++++ .../xml/line_point_formatter_with_labels.xml | 24 ++++ .../line_point_formatter_with_labels_2.xml | 24 ++++ demoapp/src/main/AndroidManifest.xml | 6 + .../com/androidplot/demos/MainActivity.java | 8 ++ .../demos/ScatterPlotActivity.java | 84 ++++++++++++ demoapp/src/main/res/layout/main.xml | 3 + .../main/res/layout/scatter_plot_example.xml | 58 +++++++++ demoapp/src/main/res/xml/point_formatter.xml | 22 ++++ .../src/main/res/xml/point_formatter_2.xml | 22 ++++ settings.gradle | 2 + 21 files changed, 705 insertions(+), 39 deletions(-) create mode 100644 demoapp-wearable/.gitignore create mode 100644 demoapp-wearable/build.gradle create mode 100644 demoapp-wearable/proguard-rules.pro create mode 100644 demoapp-wearable/src/main/AndroidManifest.xml create mode 100644 demoapp-wearable/src/main/java/com/androidplot/demo/wearable/MainActivity.java create mode 100644 demoapp-wearable/src/main/res/layout/activity_main.xml create mode 100644 demoapp-wearable/src/main/res/xml/line_point_formatter_with_labels.xml create mode 100644 demoapp-wearable/src/main/res/xml/line_point_formatter_with_labels_2.xml create mode 100644 demoapp/src/main/java/com/androidplot/demos/ScatterPlotActivity.java create mode 100644 demoapp/src/main/res/layout/scatter_plot_example.xml create mode 100644 demoapp/src/main/res/xml/point_formatter.xml create mode 100644 demoapp/src/main/res/xml/point_formatter_2.xml 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 1e29ed99..7aacb7a1 100644 --- a/androidplot-core/src/main/java/com/androidplot/util/AttrUtils.java +++ b/androidplot-core/src/main/java/com/androidplot/util/AttrUtils.java @@ -117,31 +117,19 @@ private static void configureSizeMetric(TypedArray attrs, SizeMetric model, int getSizeLayoutType(attrs, typeAttr, model.getLayoutType()); model.set(value, sizeLayoutType); - - /*if(attrs != null && attrs.hasValue(valueAttr)) { - final int valueType = attrs.peekValue(valueAttr).type; - - final SizeLayoutType sizeLayoutType = - getSizeLayoutType(attrs, typeAttr, model.getLayoutType()); - - if (valueType == TypedValue.TYPE_DIMENSION) { - model.set(attrs.getDimension(valueAttr, model.getValue()), sizeLayoutType); - } else if (valueType == TypedValue.TYPE_FLOAT) { - model.set(attrs.getFloat(valueAttr, model.getValue()), sizeLayoutType); - } else { - throw new IllegalArgumentException("Invalid size metric value type - must be float or dimension."); - } - }*/ } private static SizeLayoutType getSizeLayoutType(TypedArray attrs, int attr, SizeLayoutType defaultValue) { return SizeLayoutType.values()[attrs.getInt(attr, defaultValue.ordinal())]; } - public static void configureWidget(TypedArray attrs, Widget widget, int xLayoutStyleAttr, int xLayoutValueAttr, - int yLayoutStyleAttr, int yLayoutValueAttr, int anchorPositionAttr, - int visibilityAttr) { + public static void configureWidget(TypedArray attrs, Widget widget, int heightSizeLayoutTypeAttr, int heightAttr, + int widthSizeLayoutTypeAttr, int widthAttr, int xLayoutStyleAttr, + int xLayoutValueAttr, int yLayoutStyleAttr, int yLayoutValueAttr, + int anchorPositionAttr, int visibilityAttr) { if(attrs != null) { + configureSize(attrs, widget.getSize(), heightSizeLayoutTypeAttr, + heightAttr, widthSizeLayoutTypeAttr, widthAttr); configurePositionMetrics(attrs, widget.getPositionMetrics(), xLayoutStyleAttr, xLayoutValueAttr, yLayoutStyleAttr, yLayoutValueAttr, anchorPositionAttr); widget.setVisible(attrs.getBoolean(visibilityAttr, widget.isVisible())); 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 e0614285..48228448 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/RectRegion.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/RectRegion.java @@ -26,7 +26,8 @@ /** * RectRegion is just a rectangle with additional methods for determining - * intersections with other RectRegion instances. + * intersections with other RectRegion instances. RectRegion operates on + * "native units" which are simply whatever unit type is passed into it by the user. */ public class RectRegion { @@ -138,6 +139,31 @@ public static List regionsWithin(List regions, Number mi return intersectingRegions; } + /** + * + * @return Width of this region, in native units + */ + public Number getWidth() { + return distanceBetween(getMinX(), getMaxX()); + } + + /** + * + * @return Height of this region, in native units + */ + public Number getHeight() { + return distanceBetween(getMinY(), getMaxY()); + } + + /** + * Calculate the distance between two points in a single dimension. + * @param x + * @param y + * @return + */ + private Number distanceBetween(Number x, Number y) { + return Math.abs(x.doubleValue() - y.doubleValue()); + } public Number getMinX() { return xLineRegion.getMinVal(); 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 db6883ce..294854b2 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java @@ -281,6 +281,23 @@ protected void processAttrs(TypedArray attrs) { setRangeLabel(rangeLabelAttr); } + + // domainLabel size & position + AttrUtils.configureWidget(attrs, getDomainLabelWidget(), + R.styleable.xy_XYPlot_domainLabelHeightSizeLayoutType, R.styleable.xy_XYPlot_domainLabelHeight, + R.styleable.xy_XYPlot_domainLabelWidthSizeLayoutType, R.styleable.xy_XYPlot_domainLabelWidth, + R.styleable.xy_XYPlot_domainLabelLayoutStyleX, R.styleable.xy_XYPlot_domainLabelPositionX, + R.styleable.xy_XYPlot_domainLabelLayoutStyleY, R.styleable.xy_XYPlot_domainLabelPositionY, + R.styleable.xy_XYPlot_domainLabelAnchorPosition, R.styleable.xy_XYPlot_domainLabelVisible); + + // rangeLabel size & position + AttrUtils.configureWidget(attrs, getRangeLabelWidget(), + R.styleable.xy_XYPlot_rangeLabelHeightSizeLayoutType, R.styleable.xy_XYPlot_rangeLabelHeight, + R.styleable.xy_XYPlot_rangeLabelWidthSizeLayoutType, R.styleable.xy_XYPlot_rangeLabelWidth, + R.styleable.xy_XYPlot_rangeLabelLayoutStyleX, R.styleable.xy_XYPlot_rangeLabelPositionX, + R.styleable.xy_XYPlot_rangeLabelLayoutStyleY, R.styleable.xy_XYPlot_rangeLabelPositionY, + R.styleable.xy_XYPlot_rangeLabelAnchorPosition, R.styleable.xy_XYPlot_rangeLabelVisible); + // domainLabelPaint AttrUtils.configureTextPaint(attrs, getDomainLabelWidget().getLabelPaint(), R.styleable.xy_XYPlot_domainLabelTextColor, R.styleable.xy_XYPlot_domainLabelTextSize); @@ -319,7 +336,7 @@ protected void processAttrs(TypedArray attrs) { R.styleable.xy_XYPlot_domainOriginTickLabelTextSize); // rangeOriginTickLabelPaint - AttrUtils.configureTextPaint(attrs, getGraphWidget().getDomainOriginTickLabelPaint(), + AttrUtils.configureTextPaint(attrs, getGraphWidget().getRangeOriginTickLabelPaint(), R.styleable.xy_XYPlot_rangeOriginTickLabelTextColor, R.styleable.xy_XYPlot_rangeOriginTickLabelTextSize); @@ -333,19 +350,13 @@ protected void processAttrs(TypedArray attrs) { R.styleable.xy_XYPlot_legendIconHeightSizeLayoutType, R.styleable.xy_XYPlot_legendIconHeight, R.styleable.xy_XYPlot_legendIconWidthSizeLayoutType, R.styleable.xy_XYPlot_legendIconWidth); - // legendSize - AttrUtils.configureSize(attrs, getLegendWidget().getSize(), - R.styleable.xy_XYPlot_legendHeightSizeLayoutType, R.styleable.xy_XYPlot_legendHeight, - R.styleable.xy_XYPlot_legendWidthSizeLayoutType, R.styleable.xy_XYPlot_legendWidth); - - // legendPosition + // legend size & position AttrUtils.configureWidget(attrs, getLegendWidget(), - R.styleable.xy_XYPlot_legendLayoutStyleX, - R.styleable.xy_XYPlot_legendPositionX, - R.styleable.xy_XYPlot_legendLayoutStyleY, - R.styleable.xy_XYPlot_legendPositionY, - R.styleable.xy_XYPlot_legendAnchorPosition, - R.styleable.xy_XYPlot_legendVisible); + R.styleable.xy_XYPlot_legendHeightSizeLayoutType, R.styleable.xy_XYPlot_legendHeight, + R.styleable.xy_XYPlot_legendWidthSizeLayoutType, R.styleable.xy_XYPlot_legendWidth, + R.styleable.xy_XYPlot_legendLayoutStyleX, R.styleable.xy_XYPlot_legendPositionX, + R.styleable.xy_XYPlot_legendLayoutStyleY, R.styleable.xy_XYPlot_legendPositionY, + R.styleable.xy_XYPlot_legendAnchorPosition, R.styleable.xy_XYPlot_legendVisible); AttrUtils.configureLinePaint(attrs, getGraphWidget().getDomainGridLinePaint(), R.styleable.xy_XYPlot_graphDomainLineColor, R.styleable.xy_XYPlot_graphDomainLineThickness); diff --git a/androidplot-core/src/main/res/values/attrs.xml b/androidplot-core/src/main/res/values/attrs.xml index ac5bc983..77ab69b1 100644 --- a/androidplot-core/src/main/res/values/attrs.xml +++ b/androidplot-core/src/main/res/values/attrs.xml @@ -24,6 +24,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -36,6 +60,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -60,6 +108,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -91,12 +175,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -132,21 +240,23 @@ + + + + + + + - - - - - diff --git a/build.gradle b/build.gradle index b8118f67..ba155240 100644 --- a/build.gradle +++ b/build.gradle @@ -10,8 +10,8 @@ ext { theCompileSdkVersion = 23 theTargetSdkVersion = 23 theMinSdkVersion = 4 - theVersionName = '0.9.4' - theVersionCode = 10 + theVersionName = '0.9.5' + theVersionCode = 11 } buildscript { diff --git a/demoapp-wearable/.gitignore b/demoapp-wearable/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/demoapp-wearable/.gitignore @@ -0,0 +1 @@ +/build diff --git a/demoapp-wearable/build.gradle b/demoapp-wearable/build.gradle new file mode 100644 index 00000000..4d73b1e7 --- /dev/null +++ b/demoapp-wearable/build.gradle @@ -0,0 +1,59 @@ +/* + * 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. + */ + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.2.3' + } +} +apply plugin: 'com.android.application' + +repositories { + jcenter() +} + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.2" + + defaultConfig { + applicationId "com.androidplot.demo.wearable" + minSdkVersion 20 + targetSdkVersion 23 + versionCode 1 + versionName "1.0" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_6 + targetCompatibility JavaVersion.VERSION_1_6 + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile project(':androidplot-core') + compile 'com.google.android.support:wearable:1.3.0' + compile 'com.google.android.gms:play-services-wearable:8.3.0' +} diff --git a/demoapp-wearable/proguard-rules.pro b/demoapp-wearable/proguard-rules.pro new file mode 100644 index 00000000..321058f6 --- /dev/null +++ b/demoapp-wearable/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /usr/local/android_sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/demoapp-wearable/src/main/AndroidManifest.xml b/demoapp-wearable/src/main/AndroidManifest.xml new file mode 100644 index 00000000..9ff883a6 --- /dev/null +++ b/demoapp-wearable/src/main/AndroidManifest.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + diff --git a/demoapp-wearable/src/main/java/com/androidplot/demo/wearable/MainActivity.java b/demoapp-wearable/src/main/java/com/androidplot/demo/wearable/MainActivity.java new file mode 100644 index 00000000..ace6847f --- /dev/null +++ b/demoapp-wearable/src/main/java/com/androidplot/demo/wearable/MainActivity.java @@ -0,0 +1,92 @@ +/* + * 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.demo.wearable; + +import android.app.Activity; +import android.graphics.DashPathEffect; +import android.os.Bundle; +import com.androidplot.util.PixelUtils; +import com.androidplot.xy.*; + +import java.util.Arrays; + +/** + * A simple XY Plot styled for wearables. + */ +public class MainActivity extends Activity { + + private XYPlot plot; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + // initialize our XYPlot reference: + plot = (XYPlot) findViewById(R.id.plot); + + // create a couple arrays of y-values to plot: + Number[] series1Numbers = {1, 4, 2, 8, 4, 16, 8, 32, 16, 64}; + Number[] series2Numbers = {5, 2, 10, 5, 20, 10, 40, 20, 80, 40}; + + // turn the above arrays into XYSeries': + // (Y_VALS_ONLY means use the element index as the x value) + XYSeries series1 = new SimpleXYSeries(Arrays.asList(series1Numbers), + SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Series1"); + + XYSeries series2 = new SimpleXYSeries(Arrays.asList(series2Numbers), + SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Series2"); + + // create formatters to use for drawing a series using LineAndPointRenderer + // and configure them from xml: + LineAndPointFormatter series1Format = new LineAndPointFormatter(); + series1Format.setPointLabelFormatter(new PointLabelFormatter()); + series1Format.configure(getApplicationContext(), + R.xml.line_point_formatter_with_labels); + + LineAndPointFormatter series2Format = new LineAndPointFormatter(); + series2Format.setPointLabelFormatter(new PointLabelFormatter()); + series2Format.configure(getApplicationContext(), + R.xml.line_point_formatter_with_labels_2); + + // add an "dash" effect to the series2 line: + series2Format.getLinePaint().setPathEffect( + new DashPathEffect(new float[] { + + // always use DP when specifying pixel sizes, to keep things consistent across devices: + PixelUtils.dpToPix(20), + PixelUtils.dpToPix(15)}, 0)); + + // just for fun, add some smoothing to the lines: + // see: http://androidplot.com/smooth-curves-and-androidplot/ + series1Format.setInterpolationParams( + new CatmullRomInterpolator.Params(10, CatmullRomInterpolator.Type.Centripetal)); + + series2Format.setInterpolationParams( + new CatmullRomInterpolator.Params(10, CatmullRomInterpolator.Type.Centripetal)); + + // add a new series' to the xyplot: + plot.addSeries(series1, series1Format); + plot.addSeries(series2, series2Format); + + // 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-wearable/src/main/res/layout/activity_main.xml b/demoapp-wearable/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..c53811a1 --- /dev/null +++ b/demoapp-wearable/src/main/res/layout/activity_main.xml @@ -0,0 +1,70 @@ + + + + + + + diff --git a/demoapp-wearable/src/main/res/xml/line_point_formatter_with_labels.xml b/demoapp-wearable/src/main/res/xml/line_point_formatter_with_labels.xml new file mode 100644 index 00000000..2cf6008e --- /dev/null +++ b/demoapp-wearable/src/main/res/xml/line_point_formatter_with_labels.xml @@ -0,0 +1,24 @@ + + + + \ No newline at end of file diff --git a/demoapp-wearable/src/main/res/xml/line_point_formatter_with_labels_2.xml b/demoapp-wearable/src/main/res/xml/line_point_formatter_with_labels_2.xml new file mode 100644 index 00000000..a3851af1 --- /dev/null +++ b/demoapp-wearable/src/main/res/xml/line_point_formatter_with_labels_2.xml @@ -0,0 +1,24 @@ + + + + \ No newline at end of file diff --git a/demoapp/src/main/AndroidManifest.xml b/demoapp/src/main/AndroidManifest.xml index ca9f41c6..8ea82e53 100644 --- a/demoapp/src/main/AndroidManifest.xml +++ b/demoapp/src/main/AndroidManifest.xml @@ -46,6 +46,12 @@ + + + + + diff --git a/demoapp/src/main/java/com/androidplot/demos/MainActivity.java b/demoapp/src/main/java/com/androidplot/demos/MainActivity.java index 956ec2b1..b1ec4aae 100644 --- a/demoapp/src/main/java/com/androidplot/demos/MainActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/MainActivity.java @@ -42,6 +42,14 @@ public void onCreate(Bundle savedInstanceState) { setContentView(R.layout.main); + Button startScatterPlotExButton = (Button) findViewById(R.id.startScatterExButton); + startScatterPlotExButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + startActivity(new Intent(MainActivity.this, ScatterPlotActivity.class)); + } + }); + Button startSimplePieExButton = (Button) findViewById(R.id.startSimplePieExButton); startSimplePieExButton.setOnClickListener(new View.OnClickListener() { @Override diff --git a/demoapp/src/main/java/com/androidplot/demos/ScatterPlotActivity.java b/demoapp/src/main/java/com/androidplot/demos/ScatterPlotActivity.java new file mode 100644 index 00000000..c8e0b347 --- /dev/null +++ b/demoapp/src/main/java/com/androidplot/demos/ScatterPlotActivity.java @@ -0,0 +1,84 @@ +/* + * 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.demos; + +import android.app.Activity; +import android.os.Bundle; +import com.androidplot.xy.*; + +/** + * A scatter plot + */ +public class ScatterPlotActivity extends Activity +{ + + private XYPlot plot; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.scatter_plot_example); + + // initialize our XYPlot reference: + plot = (XYPlot) findViewById(R.id.plot); + + XYSeries series1 = generateScatter("series1", 80, new RectRegion(10, 50, 10, 50)); + XYSeries series2 = generateScatter("series2", 80, new RectRegion(30, 70, 30, 70)); + + plot.setDomainBoundaries(0, 80, BoundaryMode.FIXED); + plot.setRangeBoundaries(0, 80, BoundaryMode.FIXED); + + // create formatters to use for drawing a series using LineAndPointRenderer + // and configure them from xml: + LineAndPointFormatter series1Format = new LineAndPointFormatter(); + series1Format.configure(getApplicationContext(), + R.xml.point_formatter); + + LineAndPointFormatter series2Format = new LineAndPointFormatter(); + series2Format.configure(getApplicationContext(), + R.xml.point_formatter_2); + + // add each series to the xyplot: + plot.addSeries(series1, series1Format); + plot.addSeries(series2, series2Format); + + // reduce the number of range labels + plot.setTicksPerRangeLabel(3); + + // rotate domain labels 45 degrees to make them more compact horizontally: + plot.getGraphWidget().setDomainLabelOrientation(-45); + } + + /** + * Generate a XYSeries of random points within a specified region + * @param title + * @param numPoints + * @param region + * @return + */ + private XYSeries generateScatter(String title, int numPoints, RectRegion region) { + SimpleXYSeries series = new SimpleXYSeries(title); + for(int i = 0; i < numPoints; i++) { + series.addLast( + region.getMinX().doubleValue() + (Math.random() * region.getWidth().doubleValue()), + region.getMinY().doubleValue() + (Math.random() * region.getHeight().doubleValue()) + ); + } + return series; + } +} diff --git a/demoapp/src/main/res/layout/main.xml b/demoapp/src/main/res/layout/main.xml index a5870aec..a54f8a12 100644 --- a/demoapp/src/main/res/layout/main.xml +++ b/demoapp/src/main/res/layout/main.xml @@ -33,6 +33,9 @@