From 3e4c0bcc2b70848f5d02eac3d3c4eb09a05c5d09 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Sat, 20 Jul 2019 11:36:28 -0500 Subject: [PATCH 01/30] v1.5.7 Changes (#95) * adds convenience methods for saving and restoring pan / zoom state * adds IN_ORDER mode to BarRenderer * remove unused PlotRenderException * valuemarkers now contain their own draw logic, making it easier to customize * update project / dependencies * #94 update createBitmap to use ARGB_8888 to address ARGB_4444 deprecation warning. adds better error message on failure. * #83 Perform a null check before recycling buffered canvas instances * update app version / release notes --- .circleci/config.yml | 2 +- androidplot-core/build.gradle | 23 +- .../src/main/java/com/androidplot/Plot.java | 2078 +++++++++-------- .../exception/PlotRenderException.java | 23 - .../java/com/androidplot/pie/PieRenderer.java | 3 +- .../java/com/androidplot/pie/PieWidget.java | 3 +- .../com/androidplot/ui/LayoutManager.java | 3 +- .../com/androidplot/ui/SeriesRenderer.java | 7 +- .../androidplot/ui/widget/LegendWidget.java | 5 +- .../com/androidplot/ui/widget/Widget.java | 5 +- .../xy/AdvancedLineAndPointRenderer.java | 3 +- .../java/com/androidplot/xy/BarRenderer.java | 10 +- .../com/androidplot/xy/BubbleRenderer.java | 3 +- .../xy/FastLineAndPointRenderer.java | 3 +- .../com/androidplot/xy/GroupRenderer.java | 3 +- .../androidplot/xy/LineAndPointRenderer.java | 3 +- .../java/com/androidplot/xy/ValueMarker.java | 39 + .../java/com/androidplot/xy/XValueMarker.java | 18 + .../com/androidplot/xy/XYGraphWidget.java | 71 +- .../java/com/androidplot/xy/YValueMarker.java | 20 + .../test/java/com/androidplot/PlotTest.java | 24 +- .../java/com/androidplot/test/TestUtils.java | 6 +- .../com/androidplot/ui/widget/WidgetTest.java | 11 +- .../com/androidplot/xy/BarRendererTest.java | 45 +- .../com/androidplot/xy/XYGraphWidgetTest.java | 5 +- build.gradle | 11 +- demoapp/build.gradle | 10 +- docs/quickstart.md | 2 +- docs/release_notes.md | 9 +- gradle/wrapper/gradle-wrapper.properties | 4 +- 30 files changed, 1240 insertions(+), 1212 deletions(-) delete mode 100644 androidplot-core/src/main/java/com/androidplot/exception/PlotRenderException.java diff --git a/.circleci/config.yml b/.circleci/config.yml index c5579475..f333b5c2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -52,7 +52,7 @@ jobs: key: v1-dependencies-{{ checksum "build.gradle" }} # run tests & code coc! - - run: ./gradlew testDebug jacocoTestReportDebug + - run: ./gradlew testDebugUnitTest jacocoTestDebugUnitTestReport # build release - run: ./gradlew assembleRelease diff --git a/androidplot-core/build.gradle b/androidplot-core/build.gradle index a814a4c1..0592389b 100644 --- a/androidplot-core/build.gradle +++ b/androidplot-core/build.gradle @@ -17,8 +17,7 @@ apply plugin: 'com.android.library' apply plugin: 'com.github.dcendents.android-maven' apply plugin: 'com.jfrog.bintray' -apply plugin: 'com.vanniktech.android.junit.jacoco' -apply plugin: 'com.github.kt3k.coveralls' +apply plugin: 'jacoco-android' class AttrMarkdown extends DefaultTask { @@ -67,6 +66,14 @@ android { testApplicationId "com.androidplot.test" } + testOptions { + unitTests.all { + jacoco { + includeNoLocationClasses = true + } + } + } + /** * TODO: enable and address lint issues. */ @@ -83,15 +90,15 @@ def gitUrl = 'https://github.com/halfhp/androidplot.git' dependencies { - compile 'com.halfhp.fig:figlib:1.0.7' - compile 'com.android.support:support-annotations:27.0.2' - testCompile "org.mockito:mockito-core:1.10.19" - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile "org.robolectric:robolectric:3.1" + implementation 'com.halfhp.fig:figlib:1.0.7' + implementation 'com.android.support:support-annotations:28.0.0' + testImplementation "org.mockito:mockito-core:2.13.0" + testImplementation group: 'junit', name: 'junit', version: '4.12' + testImplementation "org.robolectric:robolectric:4.0.1" // temp fix for: // https://github.com/robolectric/robolectric/issues/1932 - testCompile 'org.khronos:opengl-api:gl1.1-android-2.1_r1' + testImplementation 'org.khronos:opengl-api:gl1.1-android-2.1_r1' } task javadoc(type: Javadoc) { diff --git a/androidplot-core/src/main/java/com/androidplot/Plot.java b/androidplot-core/src/main/java/com/androidplot/Plot.java index 5e009635..47ef0fd6 100644 --- a/androidplot-core/src/main/java/com/androidplot/Plot.java +++ b/androidplot-core/src/main/java/com/androidplot/Plot.java @@ -1,1034 +1,1044 @@ -/* - * 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; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.RectF; -import android.os.Build; -import android.os.Looper; -import android.support.annotation.Nullable; -import android.util.AttributeSet; -import android.util.Log; -import android.view.View; - -import com.androidplot.exception.PlotRenderException; -import com.androidplot.ui.Anchor; -import com.androidplot.ui.BoxModel; -import com.androidplot.ui.Formatter; -import com.androidplot.ui.HorizontalPositioning; -import com.androidplot.ui.LayoutManager; -import com.androidplot.ui.Resizable; -import com.androidplot.ui.SeriesBundle; -import com.androidplot.ui.SeriesRenderer; -import com.androidplot.ui.Size; -import com.androidplot.ui.SizeMode; -import com.androidplot.ui.TextOrientation; -import com.androidplot.ui.VerticalPositioning; -import com.androidplot.ui.widget.TextLabelWidget; -import com.androidplot.util.AttrUtils; -import com.androidplot.util.DisplayDimensions; -import com.androidplot.util.PixelUtils; -import com.halfhp.fig.Fig; -import com.halfhp.fig.FigException; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -/** - * Base class for all Plot implementations. - */ -public abstract class Plot, - RegistryType extends SeriesRegistry> - extends View implements Resizable { - private static final String TAG = Plot.class.getName(); - private static final String XML_ATTR_PREFIX = "androidplot"; - private static final String BASE_PACKAGE = "com.androidplot."; - - private static final int DEFAULT_TITLE_WIDGET_TEXT_SIZE_SP = 10; - - public DisplayDimensions getDisplayDimensions() { - return displayDims; - } - - /** - * Used for caching renderer instances. Note that once a renderer is initialized it remains initialized - * for the life of the application; does not and should not be destroyed until the application exits. - */ - public HashMap, RendererType> getRenderers() { - return renderers; - } - - /** - * Associates lists series and getFormatter pairs with the class of the Renderer used to render them. - */ - public RegistryType getRegistry() { - return registry; - } - - public void setRegistry(RegistryType registry) { - this.registry = registry; - for(BundleType bundle : registry.getSeriesAndFormatterList()) { - attachSeries(bundle.getSeries(), bundle.getFormatter()); - } - } - - /** - * - * @return A new instance of RegistryType - */ - protected abstract RegistryType getRegistryInstance(); - - public TextLabelWidget getTitle() { - return title; - } - - public void setTitle(TextLabelWidget title) { - this.title = title; - } - - public void setTitle(String title) { - getTitle().setText(title); - } - - public enum BorderStyle { - ROUNDED, - SQUARE, - NONE - } - - /** - * The RenderMode used by a Plot to draw it's self onto the screen. The RenderMode can be set - * in two ways. - * - * In an xml layout: - * - * - * - * - * - * Programatically: - * - * - * XYPlot myPlot = new XYPlot(context "MyPlot", Plot.RenderMode.USE_MAIN_THREAD); - * - * - * A Plot's RenderMode cannot be changed after the plot has been initialized. - * @since 0.5.1 - */ - public enum RenderMode { - /** - * Use a second thread and an off-screen buffer to do drawing. This is the preferred method - * of drawing dynamic data and static data that consists of a large number of points. This mode - * provides more efficient CPU utilization at the cost of increased memory usage. As of - * version 0.5.1 this is the default RenderMode. - * - * XML value: use_background_thread - * @since 0.5.1 - */ - USE_BACKGROUND_THREAD, - - /** - * Do everything in the primary thread. This is the preferred method of drawing static charts - * and dynamic data that consists of a small number of points. This mode uses less memory at - * the cost of poor CPU utilization. - * - * XML value: use_main_thread - * @since 0.5.1 - */ - USE_MAIN_THREAD - } - private BoxModel boxModel = new BoxModel(); - - // no border by default: - private BorderStyle borderStyle = Plot.BorderStyle.NONE; - private float borderRadiusX = 15; - private float borderRadiusY = 15; - private Paint borderPaint; - private Paint backgroundPaint; - private LayoutManager layoutManager; - private TextLabelWidget title; - private DisplayDimensions displayDims = new DisplayDimensions(); - private RenderMode renderMode = RenderMode.USE_MAIN_THREAD; - private final BufferedCanvas pingPong = new BufferedCanvas(); - - // used to get rid of flickering when drawing offScreenBitmap to the visible Canvas. - private final Object renderSync = new Object(); - - private HashMap, RendererType> renderers; - - private RegistryType registry; - private final ArrayList listeners; - - private Thread renderThread; - private boolean keepRunning = false; - private boolean isIdle = true; - - { - listeners = new ArrayList<>(); - registry = getRegistryInstance(); - renderers = new HashMap<>(); - - borderPaint = new Paint(); - borderPaint.setColor(Color.rgb(150, 150, 150)); - borderPaint.setStyle(Paint.Style.STROKE); - borderPaint.setStrokeWidth(1.0f); - borderPaint.setAntiAlias(true); - backgroundPaint = new Paint(); - backgroundPaint.setColor(Color.DKGRAY); - backgroundPaint.setStyle(Paint.Style.FILL); - } - - - /** - * Any rendering that utilizes a buffer from this class should synchronize rendering on the instance of this class - * that is being used. - */ - private static class BufferedCanvas { - private volatile Bitmap bgBuffer; // all drawing is done on this buffer. - private volatile Bitmap fgBuffer; - private Canvas canvas = new Canvas(); - - /** - * Call this method once drawing on a Canvas retrieved by {@link #getCanvas()} to mark - * the buffer as fully rendered. Failure to call this method will result in nothing being drawn. - */ - public synchronized void swap() { - Bitmap tmp = bgBuffer; - bgBuffer = fgBuffer; - fgBuffer = tmp; - } - - public synchronized void resize(int h, int w) { - if (w <= 0 || h <= 0) { - bgBuffer = null; - fgBuffer = null; - } else { - bgBuffer = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_4444); - fgBuffer = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_4444); - } - } - - public void recycle() { - bgBuffer.recycle(); - bgBuffer = null; - - fgBuffer.recycle(); - fgBuffer = null; - System.gc(); - } - - /** - * Get a Canvas for drawing. Actual drawing should be synchronized on the instance - * of BufferedCanvas being used. - * @return The Canvas instance to draw onto. Returns null if drawing buffers have not - * been initialized a la {@link #resize(int, int)}. - */ - public synchronized Canvas getCanvas() { - if(bgBuffer != null) { - canvas.setBitmap(bgBuffer); - return canvas; - } else { - return null; - } - } - - /** - * @return The most recent fully rendered Bitmsp - */ - public Bitmap getBitmap() { - return fgBuffer; - } - } - - /** - * Convenience constructor - wraps {@link #Plot(android.content.Context, String, com.androidplot.Plot.RenderMode)}. - * RenderMode is set to {@link RenderMode#USE_BACKGROUND_THREAD}. - * @param context - * @param title The display title of this Plot. - */ - public Plot(Context context, String title) { - this(context, title, RenderMode.USE_MAIN_THREAD); - } - - /** - * Used for programmatic instantiation. - * @param context - * @param title The display title of this Plot. - */ - public Plot(Context context, String title, RenderMode mode) { - super(context); - this.renderMode = mode; - init(context, null, 0); - getTitle().setText(title); - } - - - /** - * Required by super-class. Extending class' implementations should add - * the following code immediately before exiting to ensure that loadAttrs - * is called only once by the derived class: - * - * if(getClass().equals(DerivedPlot.class) { - * loadAttrs(context, attrs); - * } - * - * - * See {@link com.androidplot.xy.XYPlot#XYPlot(android.content.Context, android.util.AttributeSet)} - * for an example. - * @param context - * @param attrs - */ - public Plot(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs, 0); - } - - /** - * Required by super-class. Extending class' implementations should add - * the following code immediately before exiting to ensure that loadAttrs - * is called only once by the derived class: - * - * if(getClass().equals(DerivedPlot.class) { - * loadAttrs(context, attrs); - * } - * - * - * See {@link com.androidplot.xy.XYPlot#XYPlot(android.content.Context, android.util.AttributeSet, int)} - * for an example. - * @param context - * @param attrs - * @param defStyle - */ - public Plot(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(context, attrs, defStyle); - } - - /** - * Can be overridden by derived classes to control hardware acceleration state. - * Note that this setting is only used on Honeycomb and later environments. - * @return True if hardware acceleration is allowed, false otherwise. - * @since 0.5.1 - */ - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - protected boolean isHwAccelerationSupported() { - return false; - } - - /** - * Sets the render mode used by the Plot. - * WARNING: This method is not currently designed for general use outside of Configurator. - * Attempting to reassign the render mode at runtime will result in unexpected behavior. - * @param mode - */ - public void setRenderMode(RenderMode mode) { - this.renderMode = mode; - } - - /** - * Concrete implementations may do any final setup / initialization - * here. Immediately following this method's invocation, AndroidPlot assumes - * that the Plot instance is ready for final configuration via the Configurator. - */ - protected void onPreInit() { - // nothing to do by default - } - - /** - * Invoked immediately following configurator / styleable attr application. - */ - protected void onAfterConfig() { - // nothing to do by default - } - - protected final void init(Context context, AttributeSet attrs, int defStyle) { - PixelUtils.init(context); - layoutManager = new LayoutManager(); - title = new TextLabelWidget(layoutManager, new Size(25, - SizeMode.ABSOLUTE, 100, - SizeMode.ABSOLUTE), - TextOrientation.HORIZONTAL); - title.position(0, HorizontalPositioning.RELATIVE_TO_CENTER, 0, - VerticalPositioning.ABSOLUTE_FROM_TOP, Anchor.TOP_MIDDLE); - - // initialize attr defaults: - title.getLabelPaint().setTextSize( - PixelUtils.spToPix(DEFAULT_TITLE_WIDGET_TEXT_SIZE_SP)); - - onPreInit(); - // make sure the title widget is always the topmost widget: - layoutManager.moveToTop(title); - if(context != null && attrs != null) { - loadAttrs(attrs, defStyle); - } - - onAfterConfig(); - - layoutManager.onPostInit(); - if (renderMode == RenderMode.USE_BACKGROUND_THREAD) { - renderThread = new Thread(new Runnable() { - @Override - public void run() { - - keepRunning = true; - while (keepRunning) { - isIdle = false; - synchronized (pingPong) { - Canvas c = pingPong.getCanvas(); - renderOnCanvas(c); - pingPong.swap(); - } - synchronized (renderSync) { - postInvalidate(); - // prevent this thread from becoming an orphan - // after the view is destroyed - if (keepRunning) { - try { - renderSync.wait(); - } catch (InterruptedException e) { - keepRunning = false; - } - } - } - } - pingPong.recycle(); - } - }, "Androidplot renderThread"); - } - } - - /** - * If a styleable is available for the derived class, this method will be invoked with those attrs. - * The derived implementation is only responsible for setting derived class attributes, ie. it should - * not attempt to apply the Plot.title styleable attribute etc. Do not invoke recycle() on attrs. - * @param attrs Attrs for the derived class. - */ - protected abstract void processAttrs(TypedArray attrs); - - /** - * Apply base class attrs. - * @param attrs - */ - private void processBaseAttrs(TypedArray attrs) { - - // markup mode - boolean markupEnabled = attrs.getBoolean(R.styleable.Plot_markupEnabled, false); - setMarkupEnabled(markupEnabled); - - // renderMode - RenderMode renderMode = RenderMode.values() - [attrs.getInt(R.styleable.Plot_renderMode, getRenderMode().ordinal())]; - if(renderMode != getRenderMode()) { - setRenderMode(renderMode); - } - - // margins & padding - AttrUtils.configureBoxModelable(attrs, boxModel, R.styleable.Plot_marginTop, R.styleable.Plot_marginBottom, - R.styleable.Plot_marginLeft, R.styleable.Plot_marginRight, R.styleable.Plot_paddingTop, - R.styleable.Plot_paddingBottom, R.styleable.Plot_paddingLeft, R.styleable.Plot_paddingRight); - - // title - getTitle().setText(attrs.getString(R.styleable.Plot_title)); - getTitle().getLabelPaint().setTextSize( - attrs.getDimension(R.styleable.Plot_titleTextSize, - PixelUtils.spToPix(DEFAULT_TITLE_WIDGET_TEXT_SIZE_SP))); - - getTitle().getLabelPaint().setColor(attrs.getColor( - R.styleable.Plot_titleTextColor, getTitle().getLabelPaint().getColor())); - - getBackgroundPaint().setColor( - attrs.getColor(R.styleable.Plot_backgroundColor, getBackgroundPaint().getColor())); - - AttrUtils.configureLinePaint(attrs, getBorderPaint(), - R.styleable.Plot_borderColor, R.styleable.Plot_borderThickness); - } - - /** - * Parse XML Attributes. Should only be called once and at the end of the base class constructor. - * The first-pass attempts to locate styleable attributes and apply those first. After that, - * configurator-style attributes are applied, overriding any styleable attrs that may have - * been previously applied. - * - * @param attrs - */ - private void loadAttrs(AttributeSet attrs, int defStyle) { - - if (attrs != null) { - - Field styleableFieldInR = null; - TypedArray typedAttrs = null; - - Class styleableClass = R.styleable.class; - String styleableName = getClass().getName().substring(BASE_PACKAGE.length()); - styleableName = styleableName.replace('.', '_'); - try { - /** - * Use reflection to safely run for the existence of styleable defs for Plot - * and it's derivatives. This safety run is necessary to avoid runtime exceptions - * in apps that don't include Androidplot as a .aar and won't have access to - * the resources defined in the core library. - */ - styleableFieldInR = styleableClass.getField(styleableName); - } catch (NoSuchFieldException e) { - Log.d(TAG, "Styleable definition not found for: " + styleableName); - } - if (styleableFieldInR != null) { - try { - int[] resIds = (int[]) styleableFieldInR.get(null); - typedAttrs = getContext().obtainStyledAttributes(attrs, resIds, defStyle, 0); - } catch (IllegalAccessException e) { - // nothing to do - } finally { - if (typedAttrs != null) { - // apply derived class' attrs: - processAttrs(typedAttrs); - typedAttrs.recycle(); - } - } - } - - try { - styleableFieldInR = styleableClass.getField(Plot.class.getSimpleName()); - if (styleableFieldInR != null) { - int[] resIds = (int[]) styleableFieldInR.get(null); - typedAttrs = getContext().obtainStyledAttributes(attrs, resIds, defStyle, 0); - } - } catch (IllegalAccessException e) { - // nothing to do - } catch (NoSuchFieldException e) { - Log.d(TAG, "Styleable definition not found for: " + Plot.class.getSimpleName()); - } finally { - if (typedAttrs != null) { - // apply base attrs: - processBaseAttrs(typedAttrs); - typedAttrs.recycle(); - } - } - - // apply "configurator" attrs: (overrides any previously applied styleable attrs) - // filter out androidplot prefixed attrs: - HashMap attrHash = new HashMap<>(); - for (int i = 0; i < attrs.getAttributeCount(); i++) { - String attrName = attrs.getAttributeName(i); - - // case insensitive run to see if this attr begins with our prefix: - if (attrName != null && attrName.toUpperCase().startsWith(XML_ATTR_PREFIX.toUpperCase())) { - attrHash.put(attrName.substring(XML_ATTR_PREFIX.length() + 1), attrs.getAttributeValue(i)); - } - } - try { - Fig.configure(getContext(), this, attrHash); - } catch (FigException e) { - throw new RuntimeException(e); - } - } - } - - public RenderMode getRenderMode() { - return renderMode; - } - - public synchronized boolean addListener(PlotListener listener) { - return !listeners.contains(listener) && listeners.add(listener); - } - - public synchronized boolean removeListener(PlotListener listener) { - return listeners.remove(listener); - } - - protected ArrayList getListeners() { - return listeners; - } - - protected void notifyListenersBeforeDraw(Canvas canvas) { - for (PlotListener listener : listeners) { - listener.onBeforeDraw(this, canvas); - } - } - - protected void notifyListenersAfterDraw(Canvas canvas) { - for (PlotListener listener : listeners) { - listener.onAfterDraw(this, canvas); - } - } - - /** - * Convenience method to add a multiple series at once using the same formatter. - * If a problem is encountered, the method immediately returns false and the plot - * will contain whatever series were added before the failure. - * @param formatter - * @param series - * @return True if all series were successfully added, false otherwise. - * @since 0.9.7 - */ - public synchronized boolean addSeries(FormatterType formatter, SeriesType... series) { - for(SeriesType s : series) { - if(!addSeries(s, formatter)) { - return false; - } - } - return true; - } - - /** - * Add a new Series to the Plot. - * @param series - * @param formatter - * @return True if the series was added or false if the series / formatter pair already exists in the registry. - */ - public synchronized boolean addSeries(SeriesType series, FormatterType formatter) { - final boolean result = getRegistry().add(series, formatter); - attachSeries(series, formatter); - return result; - } - - protected void attachSeries(SeriesType series, FormatterType formatter) { - - Class rendererClass = formatter.getRendererClass(); - - // initialize the Renderer if necessary: - if(!getRenderers().containsKey(rendererClass)) { - getRenderers().put(rendererClass, (RendererType) formatter.getRendererInstance(this)); - } - - // if this series implements PlotListener, add it as a listener: - if(series instanceof PlotListener) { - addListener((PlotListener)series); - } - } - - /** - * - * @param series - * @param rendererClass - * @return The {@link SeriesBundle} that matches the series and rendererClass params, or null if one is not found. - */ - protected SeriesBundle getSeries(SeriesType series, Class rendererClass) { - for(SeriesBundle thisPair : getSeries(series)) { - if(thisPair.getFormatter().getRendererClass() == rendererClass) { - return thisPair; - } - } - return null; - } - - /** - * - * @param series - * @return A List of {@link SeriesBundle} instances that reference series. - */ - protected List> getSeries(SeriesType series) { - return getRegistry().get(series); - } - - /** - * - * Remove a series for a specific Renderer only. Use {@link #removeSeries(Series)} to remove the series - * from the plot completely. - * @param series - * @param rendererClass - * @return True if anything was removed, false otherwise - */ - public synchronized boolean removeSeries(SeriesType series, Class rendererClass) { - - List removedItems = getRegistry().remove(series, rendererClass); - - // if series implements PlotListener and is not assigned to any other renderers remove it as a listener: - if (removedItems.size() == 1 && series instanceof PlotListener) { - removeListener((PlotListener) series); - return true; - } - return false; - } - - /** - * Remove all occurrences of series regardless of the associated Renderer. - * @param series - */ - public synchronized void removeSeries(SeriesType series) { - // if series implements PlotListener, remove it from listeners: - if (series instanceof PlotListener) { - removeListener((PlotListener) series); - } - - getRegistry().remove(series); - } - - /** - * Remove all series from the plot. - */ - public void clear() { - for(SeriesType series : getRegistry().getSeriesList()) { - if(series instanceof PlotListener) { - removeListener((PlotListener) series); - } - } - getRegistry().clear(); - } - - public boolean isEmpty() { - return getRegistry().isEmpty(); - } - - /** - * - * @param series - * @param rendererClass - * @return The Formatter instance corresponding to the specified series / renderer pair. - */ - public FormatterType getFormatter(SeriesType series, Class rendererClass) { - return getSeries(series, rendererClass).getFormatter(); - } - - public T getRenderer(Class rendererClass) { - return (T) getRenderers().get(rendererClass); - } - - public List getRendererList() { - return new ArrayList<>(getRenderers().values()); - } - - public void setMarkupEnabled(boolean enabled) { - this.layoutManager.setMarkupEnabled(enabled); - } - - /** - * Causes the plot to be redrawn. - * @since 0.5.1 - */ - public void redraw() { - - if (renderMode == RenderMode.USE_BACKGROUND_THREAD) { - - // only enter synchronized block if the call is expected to block OR - // if the render thread is idle, so we know that we won't have to wait to - // obtain a lock. - if (isIdle) { - synchronized (renderSync) { - renderSync.notify(); - } - } - } else if(renderMode == RenderMode.USE_MAIN_THREAD) { - - // are we on the UI thread? - if (Looper.myLooper() == Looper.getMainLooper()) { - invalidate(); - } else { - postInvalidate(); - } - } else { - throw new IllegalArgumentException("Unsupported Render Mode: " + renderMode); - } - } - - @Override - public synchronized void layout(final DisplayDimensions dims) { - this.displayDims = dims; - layoutManager.layout(displayDims); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - synchronized(renderSync) { - keepRunning = false; - renderSync.notify(); - } - } - - - @Override - protected synchronized void onSizeChanged (int w, int h, int oldw, int oldh) { - - // update pixel conversion values - PixelUtils.init(getContext()); - - // disable hardware acceleration if it's not explicitly supported - // by the current Plot implementation. this run only applies to - // honeycomb and later environments. - if (Build.VERSION.SDK_INT >= 11) { - if (!isHwAccelerationSupported() && isHardwareAccelerated()) { - setLayerType(View.LAYER_TYPE_SOFTWARE, null); - } - } - - // pingPong is only used in background rendering mode. - if(renderMode == RenderMode.USE_BACKGROUND_THREAD) { - pingPong.resize(h, w); - } - - RectF cRect = new RectF(0, 0, w, h); - RectF mRect = boxModel.getMarginatedRect(cRect); - RectF pRect = boxModel.getPaddedRect(mRect); - - layout(new DisplayDimensions(cRect, mRect, pRect)); - super.onSizeChanged(w, h, oldw, oldh); - if(renderThread != null && !renderThread.isAlive()) { - renderThread.start(); - } - } - - /** - * Called whenever the plot needs to be drawn via the Handler, which invokes invalidate(). - * Should never be called directly; use {@link #redraw()} instead. - * @param canvas - */ - @Override - protected void onDraw(Canvas canvas) { - if (renderMode == RenderMode.USE_BACKGROUND_THREAD) { - synchronized(pingPong) { - Bitmap bmp = pingPong.getBitmap(); - if(bmp != null) { - canvas.drawBitmap(bmp, 0, 0, null); - } - } - } else if (renderMode == RenderMode.USE_MAIN_THREAD) { - renderOnCanvas(canvas); - } else { - throw new IllegalArgumentException("Unsupported Render Mode: " + renderMode); - } - } - - /** - * Renders the plot onto a canvas. Used by both main thread to draw directly - * onto the View's canvas as well as by background draw to render onto a - * Bitmap buffer. At the end of the day this is the main entry for a plot's - * "heavy lifting". - * @param canvas - */ - protected synchronized void renderOnCanvas(@Nullable Canvas canvas) { - if(canvas == null) { - return; - } - try { - // any series interested in synchronizing with plot should - // implement PlotListener.onBeforeDraw(...) and do a read lock from within its - // invocation. This is the entry point into that call: - notifyListenersBeforeDraw(canvas); - try { - // need to completely erase what was on the canvas before redrawing, otherwise - // some odd aliasing artifacts begin to build up around the edges of aa'd entities - // over time. - canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); - if (backgroundPaint != null) { - drawBackground(canvas, displayDims.marginatedRect); - } - - layoutManager.draw(canvas); - - if (getBorderPaint() != null) { - drawBorder(canvas, displayDims.marginatedRect); - } - } catch (PlotRenderException e) { - Log.e(TAG, "Exception while rendering Plot.", e); - } catch (Exception e) { - Log.e(TAG, "Exception while rendering Plot.", e); - } - - isIdle = true; - // any series interested in synchronizing with plot should - // implement PlotListener.onAfterDraw(...) and do a read unlock from within that - // invocation. This is the entry point for that invocation. - notifyListenersAfterDraw(canvas); - } finally { - } - } - - - /** - * Sets the visual style of the plot's border. - * @param style - * @param radiusX Sets the X radius for BorderStyle.ROUNDED. Use null for all other styles. - * @param radiusY Sets the Y radius for BorderStyle.ROUNDED. Use null for all other styles. - */ - public void setBorderStyle(BorderStyle style, Float radiusX, Float radiusY) { - if (style == Plot.BorderStyle.ROUNDED) { - if (radiusX == null || radiusY == null){ - throw new IllegalArgumentException("radiusX and radiusY cannot be null when using BorderStyle.ROUNDED"); - } - this.borderRadiusX = radiusX; - this.borderRadiusY = radiusY; - } - this.borderStyle = style; - } - - /** - * Draws the plot's outer border. - * @param canvas - * @throws PlotRenderException - */ - protected void drawBorder(Canvas canvas, RectF dims) { - drawRect(canvas, dims, borderPaint); - } - - protected void drawBackground(Canvas canvas, RectF dims) { - drawRect(canvas, dims, backgroundPaint); - } - - protected void drawRect(Canvas canvas, RectF dims, Paint paint) { - switch (borderStyle) { - case ROUNDED: - canvas.drawRoundRect(dims, borderRadiusX, borderRadiusY, paint); - break; - case SQUARE: - default: - canvas.drawRect(dims, paint); - break; - } - } - - public LayoutManager getLayoutManager() { - return layoutManager; - } - - public void setLayoutManager(LayoutManager layoutManager) { - this.layoutManager = layoutManager; - } - - public Paint getBackgroundPaint() { - return backgroundPaint; - } - - public void setBackgroundPaint(Paint backgroundPaint) { - this.backgroundPaint = backgroundPaint; - } - - /** - * Convenience method - wraps the individual setMarginXXX methods into a single method. - * @param left - * @param top - * @param right - * @param bottom - */ - public void setPlotMargins(float left, float top, float right, float bottom) { - setPlotMarginLeft(left); - setPlotMarginTop(top); - setPlotMarginRight(right); - setPlotMarginBottom(bottom); - } - - /** - * Convenience method - wraps the individual setPaddingXXX methods into a single method. - * @param left - * @param top - * @param right - * @param bottom - */ - public void setPlotPadding(float left, float top, float right, float bottom) { - setPlotPaddingLeft(left); - setPlotPaddingTop(top); - setPlotPaddingRight(right); - setPlotPaddingBottom(bottom); - } - - public float getPlotMarginTop() { - return boxModel.getMarginTop(); - } - - public void setPlotMarginTop(float plotMarginTop) { - boxModel.setMarginTop(plotMarginTop); - } - - public float getPlotMarginBottom() { - return boxModel.getMarginBottom(); - } - - public void setPlotMarginBottom(float plotMarginBottom) { - boxModel.setMarginBottom(plotMarginBottom); - } - - public float getPlotMarginLeft() { - return boxModel.getMarginLeft(); - } - - public void setPlotMarginLeft(float plotMarginLeft) { - boxModel.setMarginLeft(plotMarginLeft); - } - - public float getPlotMarginRight() { - return boxModel.getMarginRight(); - } - - public void setPlotMarginRight(float plotMarginRight) { - boxModel.setMarginRight(plotMarginRight); - } - - public float getPlotPaddingTop() { - return boxModel.getPaddingTop(); - } - - public void setPlotPaddingTop(float plotPaddingTop) { - boxModel.setPaddingTop(plotPaddingTop); - } - - public float getPlotPaddingBottom() { - return boxModel.getPaddingBottom(); - } - - public void setPlotPaddingBottom(float plotPaddingBottom) { - boxModel.setPaddingBottom(plotPaddingBottom); - } - - public float getPlotPaddingLeft() { - return boxModel.getPaddingLeft(); - } - - public void setPlotPaddingLeft(float plotPaddingLeft) { - boxModel.setPaddingLeft(plotPaddingLeft); - } - - public float getPlotPaddingRight() { - return boxModel.getPaddingRight(); - } - - public void setPlotPaddingRight(float plotPaddingRight) { - boxModel.setPaddingRight(plotPaddingRight); - } - - public Paint getBorderPaint() { - return borderPaint; - } - - /** - * Set's the paint used to draw the border. Note that this method - * copies borderPaint and set's the copy's Paint.Style attribute to - * Paint.Style.STROKE. - * @param borderPaint - */ - public void setBorderPaint(Paint borderPaint) { - if(borderPaint == null) { - this.borderPaint = null; - } else { - this.borderPaint = new Paint(borderPaint); - this.borderPaint.setStyle(Paint.Style.STROKE); - } - } -} +/* + * 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; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.RectF; +import android.os.Build; +import android.os.Looper; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; + +import com.androidplot.ui.Anchor; +import com.androidplot.ui.BoxModel; +import com.androidplot.ui.Formatter; +import com.androidplot.ui.HorizontalPositioning; +import com.androidplot.ui.LayoutManager; +import com.androidplot.ui.Resizable; +import com.androidplot.ui.SeriesBundle; +import com.androidplot.ui.SeriesRenderer; +import com.androidplot.ui.Size; +import com.androidplot.ui.SizeMode; +import com.androidplot.ui.TextOrientation; +import com.androidplot.ui.VerticalPositioning; +import com.androidplot.ui.widget.TextLabelWidget; +import com.androidplot.util.AttrUtils; +import com.androidplot.util.DisplayDimensions; +import com.androidplot.util.PixelUtils; +import com.halfhp.fig.Fig; +import com.halfhp.fig.FigException; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * Base class for all Plot implementations. + */ +public abstract class Plot, + RegistryType extends SeriesRegistry> + extends View implements Resizable { + private static final String TAG = Plot.class.getName(); + private static final String XML_ATTR_PREFIX = "androidplot"; + private static final String BASE_PACKAGE = "com.androidplot."; + + private static final int DEFAULT_TITLE_WIDGET_TEXT_SIZE_SP = 10; + + public DisplayDimensions getDisplayDimensions() { + return displayDims; + } + + /** + * Used for caching renderer instances. Note that once a renderer is initialized it remains initialized + * for the life of the application; does not and should not be destroyed until the application exits. + */ + public HashMap, RendererType> getRenderers() { + return renderers; + } + + /** + * Associates lists series and getFormatter pairs with the class of the Renderer used to render them. + */ + public RegistryType getRegistry() { + return registry; + } + + public void setRegistry(RegistryType registry) { + this.registry = registry; + for(BundleType bundle : registry.getSeriesAndFormatterList()) { + attachSeries(bundle.getSeries(), bundle.getFormatter()); + } + } + + /** + * + * @return A new instance of RegistryType + */ + protected abstract RegistryType getRegistryInstance(); + + public TextLabelWidget getTitle() { + return title; + } + + public void setTitle(TextLabelWidget title) { + this.title = title; + } + + public void setTitle(String title) { + getTitle().setText(title); + } + + public enum BorderStyle { + ROUNDED, + SQUARE, + NONE + } + + /** + * The RenderMode used by a Plot to draw it's self onto the screen. The RenderMode can be set + * in two ways. + * + * In an xml layout: + * + * + * + * + * + * Programatically: + * + * + * XYPlot myPlot = new XYPlot(context "MyPlot", Plot.RenderMode.USE_MAIN_THREAD); + * + * + * A Plot's RenderMode cannot be changed after the plot has been initialized. + * @since 0.5.1 + */ + public enum RenderMode { + /** + * Use a second thread and an off-screen buffer to do drawing. This is the preferred method + * of drawing dynamic data and static data that consists of a large number of points. This mode + * provides more efficient CPU utilization at the cost of increased memory usage. As of + * version 0.5.1 this is the default RenderMode. + * + * XML value: use_background_thread + * @since 0.5.1 + */ + USE_BACKGROUND_THREAD, + + /** + * Do everything in the primary thread. This is the preferred method of drawing static charts + * and dynamic data that consists of a small number of points. This mode uses less memory at + * the cost of poor CPU utilization. + * + * XML value: use_main_thread + * @since 0.5.1 + */ + USE_MAIN_THREAD + } + private BoxModel boxModel = new BoxModel(); + + // no border by default: + private BorderStyle borderStyle = Plot.BorderStyle.NONE; + private float borderRadiusX = 15; + private float borderRadiusY = 15; + private Paint borderPaint; + private Paint backgroundPaint; + private LayoutManager layoutManager; + private TextLabelWidget title; + private DisplayDimensions displayDims = new DisplayDimensions(); + private RenderMode renderMode = RenderMode.USE_MAIN_THREAD; + private final BufferedCanvas pingPong = new BufferedCanvas(); + + // used to get rid of flickering when drawing offScreenBitmap to the visible Canvas. + private final Object renderSync = new Object(); + + private HashMap, RendererType> renderers; + + private RegistryType registry; + private final ArrayList listeners; + + private Thread renderThread; + private boolean keepRunning = false; + private boolean isIdle = true; + + { + listeners = new ArrayList<>(); + registry = getRegistryInstance(); + renderers = new HashMap<>(); + + borderPaint = new Paint(); + borderPaint.setColor(Color.rgb(150, 150, 150)); + borderPaint.setStyle(Paint.Style.STROKE); + borderPaint.setStrokeWidth(1.0f); + borderPaint.setAntiAlias(true); + backgroundPaint = new Paint(); + backgroundPaint.setColor(Color.DKGRAY); + backgroundPaint.setStyle(Paint.Style.FILL); + } + + + /** + * Any rendering that utilizes a buffer from this class should synchronize rendering on the instance of this class + * that is being used. + */ + private static class BufferedCanvas { + private volatile Bitmap bgBuffer; // all drawing is done on this buffer. + private volatile Bitmap fgBuffer; + private Canvas canvas = new Canvas(); + + /** + * Call this method once drawing on a Canvas retrieved by {@link #getCanvas()} to mark + * the buffer as fully rendered. Failure to call this method will result in nothing being drawn. + */ + public synchronized void swap() { + Bitmap tmp = bgBuffer; + bgBuffer = fgBuffer; + fgBuffer = tmp; + } + + public synchronized void resize(int h, int w) { + if (w <= 0 || h <= 0) { + bgBuffer = null; + fgBuffer = null; + } else { + try { + bgBuffer = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + fgBuffer = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + } catch(IllegalArgumentException e) { + throw new IllegalArgumentException("Illegal argument passed to Bitmap.createBitmap. width: " + w + " height: " + h); + } + } + } + + public void recycle() { + /** + * TODO: Issue #93 There have been rare reports of NPE's originating from here. + * Most likely there is something deeper that is amiss, but for now we'll simply + * do a null check before recycling. + */ + if(bgBuffer != null) { + bgBuffer.recycle(); + bgBuffer = null; + } + + if(fgBuffer != null) { + fgBuffer.recycle(); + fgBuffer = null; + } + + System.gc(); + } + + /** + * Get a Canvas for drawing. Actual drawing should be synchronized on the instance + * of BufferedCanvas being used. + * @return The Canvas instance to draw onto. Returns null if drawing buffers have not + * been initialized a la {@link #resize(int, int)}. + */ + public synchronized Canvas getCanvas() { + if(bgBuffer != null) { + canvas.setBitmap(bgBuffer); + return canvas; + } else { + return null; + } + } + + /** + * @return The most recent fully rendered Bitmsp + */ + public Bitmap getBitmap() { + return fgBuffer; + } + } + + /** + * Convenience constructor - wraps {@link #Plot(android.content.Context, String, com.androidplot.Plot.RenderMode)}. + * RenderMode is set to {@link RenderMode#USE_BACKGROUND_THREAD}. + * @param context + * @param title The display title of this Plot. + */ + public Plot(Context context, String title) { + this(context, title, RenderMode.USE_MAIN_THREAD); + } + + /** + * Used for programmatic instantiation. + * @param context + * @param title The display title of this Plot. + */ + public Plot(Context context, String title, RenderMode mode) { + super(context); + this.renderMode = mode; + init(context, null, 0); + getTitle().setText(title); + } + + + /** + * Required by super-class. Extending class' implementations should add + * the following code immediately before exiting to ensure that loadAttrs + * is called only once by the derived class: + * + * if(getClass().equals(DerivedPlot.class) { + * loadAttrs(context, attrs); + * } + * + * + * See {@link com.androidplot.xy.XYPlot#XYPlot(android.content.Context, android.util.AttributeSet)} + * for an example. + * @param context + * @param attrs + */ + public Plot(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, 0); + } + + /** + * Required by super-class. Extending class' implementations should add + * the following code immediately before exiting to ensure that loadAttrs + * is called only once by the derived class: + * + * if(getClass().equals(DerivedPlot.class) { + * loadAttrs(context, attrs); + * } + * + * + * See {@link com.androidplot.xy.XYPlot#XYPlot(android.content.Context, android.util.AttributeSet, int)} + * for an example. + * @param context + * @param attrs + * @param defStyle + */ + public Plot(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context, attrs, defStyle); + } + + /** + * Can be overridden by derived classes to control hardware acceleration state. + * Note that this setting is only used on Honeycomb and later environments. + * @return True if hardware acceleration is allowed, false otherwise. + * @since 0.5.1 + */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + protected boolean isHwAccelerationSupported() { + return false; + } + + /** + * Sets the render mode used by the Plot. + * WARNING: This method is not currently designed for general use outside of Configurator. + * Attempting to reassign the render mode at runtime will result in unexpected behavior. + * @param mode + */ + public void setRenderMode(RenderMode mode) { + this.renderMode = mode; + } + + /** + * Concrete implementations may do any final setup / initialization + * here. Immediately following this method's invocation, AndroidPlot assumes + * that the Plot instance is ready for final configuration via the Configurator. + */ + protected void onPreInit() { + // nothing to do by default + } + + /** + * Invoked immediately following configurator / styleable attr application. + */ + protected void onAfterConfig() { + // nothing to do by default + } + + protected final void init(Context context, AttributeSet attrs, int defStyle) { + PixelUtils.init(context); + layoutManager = new LayoutManager(); + title = new TextLabelWidget(layoutManager, new Size(25, + SizeMode.ABSOLUTE, 100, + SizeMode.ABSOLUTE), + TextOrientation.HORIZONTAL); + title.position(0, HorizontalPositioning.RELATIVE_TO_CENTER, 0, + VerticalPositioning.ABSOLUTE_FROM_TOP, Anchor.TOP_MIDDLE); + + // initialize attr defaults: + title.getLabelPaint().setTextSize( + PixelUtils.spToPix(DEFAULT_TITLE_WIDGET_TEXT_SIZE_SP)); + + onPreInit(); + // make sure the title widget is always the topmost widget: + layoutManager.moveToTop(title); + if(context != null && attrs != null) { + loadAttrs(attrs, defStyle); + } + + onAfterConfig(); + + layoutManager.onPostInit(); + if (renderMode == RenderMode.USE_BACKGROUND_THREAD) { + renderThread = new Thread(new Runnable() { + @Override + public void run() { + + keepRunning = true; + while (keepRunning) { + isIdle = false; + synchronized (pingPong) { + Canvas c = pingPong.getCanvas(); + renderOnCanvas(c); + pingPong.swap(); + } + synchronized (renderSync) { + postInvalidate(); + // prevent this thread from becoming an orphan + // after the view is destroyed + if (keepRunning) { + try { + renderSync.wait(); + } catch (InterruptedException e) { + keepRunning = false; + } + } + } + } + pingPong.recycle(); + } + }, "Androidplot renderThread"); + } + } + + /** + * If a styleable is available for the derived class, this method will be invoked with those attrs. + * The derived implementation is only responsible for setting derived class attributes, ie. it should + * not attempt to apply the Plot.title styleable attribute etc. Do not invoke recycle() on attrs. + * @param attrs Attrs for the derived class. + */ + protected abstract void processAttrs(TypedArray attrs); + + /** + * Apply base class attrs. + * @param attrs + */ + private void processBaseAttrs(TypedArray attrs) { + + // markup mode + boolean markupEnabled = attrs.getBoolean(R.styleable.Plot_markupEnabled, false); + setMarkupEnabled(markupEnabled); + + // renderMode + RenderMode renderMode = RenderMode.values() + [attrs.getInt(R.styleable.Plot_renderMode, getRenderMode().ordinal())]; + if(renderMode != getRenderMode()) { + setRenderMode(renderMode); + } + + // margins & padding + AttrUtils.configureBoxModelable(attrs, boxModel, R.styleable.Plot_marginTop, R.styleable.Plot_marginBottom, + R.styleable.Plot_marginLeft, R.styleable.Plot_marginRight, R.styleable.Plot_paddingTop, + R.styleable.Plot_paddingBottom, R.styleable.Plot_paddingLeft, R.styleable.Plot_paddingRight); + + // title + getTitle().setText(attrs.getString(R.styleable.Plot_title)); + getTitle().getLabelPaint().setTextSize( + attrs.getDimension(R.styleable.Plot_titleTextSize, + PixelUtils.spToPix(DEFAULT_TITLE_WIDGET_TEXT_SIZE_SP))); + + getTitle().getLabelPaint().setColor(attrs.getColor( + R.styleable.Plot_titleTextColor, getTitle().getLabelPaint().getColor())); + + getBackgroundPaint().setColor( + attrs.getColor(R.styleable.Plot_backgroundColor, getBackgroundPaint().getColor())); + + AttrUtils.configureLinePaint(attrs, getBorderPaint(), + R.styleable.Plot_borderColor, R.styleable.Plot_borderThickness); + } + + /** + * Parse XML Attributes. Should only be called once and at the end of the base class constructor. + * The first-pass attempts to locate styleable attributes and apply those first. After that, + * configurator-style attributes are applied, overriding any styleable attrs that may have + * been previously applied. + * + * @param attrs + */ + private void loadAttrs(AttributeSet attrs, int defStyle) { + + if (attrs != null) { + + Field styleableFieldInR = null; + TypedArray typedAttrs = null; + + Class styleableClass = R.styleable.class; + String styleableName = getClass().getName().substring(BASE_PACKAGE.length()); + styleableName = styleableName.replace('.', '_'); + try { + /** + * Use reflection to safely run for the existence of styleable defs for Plot + * and it's derivatives. This safety run is necessary to avoid runtime exceptions + * in apps that don't include Androidplot as a .aar and won't have access to + * the resources defined in the core library. + */ + styleableFieldInR = styleableClass.getField(styleableName); + } catch (NoSuchFieldException e) { + Log.d(TAG, "Styleable definition not found for: " + styleableName); + } + if (styleableFieldInR != null) { + try { + int[] resIds = (int[]) styleableFieldInR.get(null); + typedAttrs = getContext().obtainStyledAttributes(attrs, resIds, defStyle, 0); + } catch (IllegalAccessException e) { + // nothing to do + } finally { + if (typedAttrs != null) { + // apply derived class' attrs: + processAttrs(typedAttrs); + typedAttrs.recycle(); + } + } + } + + try { + styleableFieldInR = styleableClass.getField(Plot.class.getSimpleName()); + if (styleableFieldInR != null) { + int[] resIds = (int[]) styleableFieldInR.get(null); + typedAttrs = getContext().obtainStyledAttributes(attrs, resIds, defStyle, 0); + } + } catch (IllegalAccessException e) { + // nothing to do + } catch (NoSuchFieldException e) { + Log.d(TAG, "Styleable definition not found for: " + Plot.class.getSimpleName()); + } finally { + if (typedAttrs != null) { + // apply base attrs: + processBaseAttrs(typedAttrs); + typedAttrs.recycle(); + } + } + + // apply "configurator" attrs: (overrides any previously applied styleable attrs) + // filter out androidplot prefixed attrs: + HashMap attrHash = new HashMap<>(); + for (int i = 0; i < attrs.getAttributeCount(); i++) { + String attrName = attrs.getAttributeName(i); + + // case insensitive run to see if this attr begins with our prefix: + if (attrName != null && attrName.toUpperCase().startsWith(XML_ATTR_PREFIX.toUpperCase())) { + attrHash.put(attrName.substring(XML_ATTR_PREFIX.length() + 1), attrs.getAttributeValue(i)); + } + } + try { + Fig.configure(getContext(), this, attrHash); + } catch (FigException e) { + throw new RuntimeException(e); + } + } + } + + public RenderMode getRenderMode() { + return renderMode; + } + + public synchronized boolean addListener(PlotListener listener) { + return !listeners.contains(listener) && listeners.add(listener); + } + + public synchronized boolean removeListener(PlotListener listener) { + return listeners.remove(listener); + } + + protected ArrayList getListeners() { + return listeners; + } + + protected void notifyListenersBeforeDraw(Canvas canvas) { + for (PlotListener listener : listeners) { + listener.onBeforeDraw(this, canvas); + } + } + + protected void notifyListenersAfterDraw(Canvas canvas) { + for (PlotListener listener : listeners) { + listener.onAfterDraw(this, canvas); + } + } + + /** + * Convenience method to add a multiple series at once using the same formatter. + * If a problem is encountered, the method immediately returns false and the plot + * will contain whatever series were added before the failure. + * @param formatter + * @param series + * @return True if all series were successfully added, false otherwise. + * @since 0.9.7 + */ + public synchronized boolean addSeries(FormatterType formatter, SeriesType... series) { + for(SeriesType s : series) { + if(!addSeries(s, formatter)) { + return false; + } + } + return true; + } + + /** + * Add a new Series to the Plot. + * @param series + * @param formatter + * @return True if the series was added or false if the series / formatter pair already exists in the registry. + */ + public synchronized boolean addSeries(SeriesType series, FormatterType formatter) { + final boolean result = getRegistry().add(series, formatter); + attachSeries(series, formatter); + return result; + } + + protected void attachSeries(SeriesType series, FormatterType formatter) { + + Class rendererClass = formatter.getRendererClass(); + + // initialize the Renderer if necessary: + if(!getRenderers().containsKey(rendererClass)) { + getRenderers().put(rendererClass, (RendererType) formatter.getRendererInstance(this)); + } + + // if this series implements PlotListener, add it as a listener: + if(series instanceof PlotListener) { + addListener((PlotListener)series); + } + } + + /** + * + * @param series + * @param rendererClass + * @return The {@link SeriesBundle} that matches the series and rendererClass params, or null if one is not found. + */ + protected SeriesBundle getSeries(SeriesType series, Class rendererClass) { + for(SeriesBundle thisPair : getSeries(series)) { + if(thisPair.getFormatter().getRendererClass() == rendererClass) { + return thisPair; + } + } + return null; + } + + /** + * + * @param series + * @return A List of {@link SeriesBundle} instances that reference series. + */ + protected List> getSeries(SeriesType series) { + return getRegistry().get(series); + } + + /** + * + * Remove a series for a specific Renderer only. Use {@link #removeSeries(Series)} to remove the series + * from the plot completely. + * @param series + * @param rendererClass + * @return True if anything was removed, false otherwise + */ + public synchronized boolean removeSeries(SeriesType series, Class rendererClass) { + + List removedItems = getRegistry().remove(series, rendererClass); + + // if series implements PlotListener and is not assigned to any other renderers remove it as a listener: + if (removedItems.size() == 1 && series instanceof PlotListener) { + removeListener((PlotListener) series); + return true; + } + return false; + } + + /** + * Remove all occurrences of series regardless of the associated Renderer. + * @param series + */ + public synchronized void removeSeries(SeriesType series) { + // if series implements PlotListener, remove it from listeners: + if (series instanceof PlotListener) { + removeListener((PlotListener) series); + } + + getRegistry().remove(series); + } + + /** + * Remove all series from the plot. + */ + public void clear() { + for(SeriesType series : getRegistry().getSeriesList()) { + if(series instanceof PlotListener) { + removeListener((PlotListener) series); + } + } + getRegistry().clear(); + } + + public boolean isEmpty() { + return getRegistry().isEmpty(); + } + + /** + * + * @param series + * @param rendererClass + * @return The Formatter instance corresponding to the specified series / renderer pair. + */ + public FormatterType getFormatter(SeriesType series, Class rendererClass) { + return getSeries(series, rendererClass).getFormatter(); + } + + public T getRenderer(Class rendererClass) { + return (T) getRenderers().get(rendererClass); + } + + public List getRendererList() { + return new ArrayList<>(getRenderers().values()); + } + + public void setMarkupEnabled(boolean enabled) { + this.layoutManager.setMarkupEnabled(enabled); + } + + /** + * Causes the plot to be redrawn. + * @since 0.5.1 + */ + public void redraw() { + + if (renderMode == RenderMode.USE_BACKGROUND_THREAD) { + + // only enter synchronized block if the call is expected to block OR + // if the render thread is idle, so we know that we won't have to wait to + // obtain a lock. + if (isIdle) { + synchronized (renderSync) { + renderSync.notify(); + } + } + } else if(renderMode == RenderMode.USE_MAIN_THREAD) { + + // are we on the UI thread? + if (Looper.myLooper() == Looper.getMainLooper()) { + invalidate(); + } else { + postInvalidate(); + } + } else { + throw new IllegalArgumentException("Unsupported Render Mode: " + renderMode); + } + } + + @Override + public synchronized void layout(final DisplayDimensions dims) { + this.displayDims = dims; + layoutManager.layout(displayDims); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + synchronized(renderSync) { + keepRunning = false; + renderSync.notify(); + } + } + + + @Override + protected synchronized void onSizeChanged (int w, int h, int oldw, int oldh) { + + // update pixel conversion values + PixelUtils.init(getContext()); + + // disable hardware acceleration if it's not explicitly supported + // by the current Plot implementation. this run only applies to + // honeycomb and later environments. + if (Build.VERSION.SDK_INT >= 11) { + if (!isHwAccelerationSupported() && isHardwareAccelerated()) { + setLayerType(View.LAYER_TYPE_SOFTWARE, null); + } + } + + // pingPong is only used in background rendering mode. + if(renderMode == RenderMode.USE_BACKGROUND_THREAD) { + pingPong.resize(h, w); + } + + RectF cRect = new RectF(0, 0, w, h); + RectF mRect = boxModel.getMarginatedRect(cRect); + RectF pRect = boxModel.getPaddedRect(mRect); + + layout(new DisplayDimensions(cRect, mRect, pRect)); + super.onSizeChanged(w, h, oldw, oldh); + if(renderThread != null && !renderThread.isAlive()) { + renderThread.start(); + } + } + + /** + * Called whenever the plot needs to be drawn via the Handler, which invokes invalidate(). + * Should never be called directly; use {@link #redraw()} instead. + * @param canvas + */ + @Override + protected void onDraw(Canvas canvas) { + if (renderMode == RenderMode.USE_BACKGROUND_THREAD) { + synchronized(pingPong) { + Bitmap bmp = pingPong.getBitmap(); + if(bmp != null) { + canvas.drawBitmap(bmp, 0, 0, null); + } + } + } else if (renderMode == RenderMode.USE_MAIN_THREAD) { + renderOnCanvas(canvas); + } else { + throw new IllegalArgumentException("Unsupported Render Mode: " + renderMode); + } + } + + /** + * Renders the plot onto a canvas. Used by both main thread to draw directly + * onto the View's canvas as well as by background draw to render onto a + * Bitmap buffer. At the end of the day this is the main entry for a plot's + * "heavy lifting". + * @param canvas + */ + protected synchronized void renderOnCanvas(@Nullable Canvas canvas) { + if(canvas == null) { + return; + } + try { + // any series interested in synchronizing with plot should + // implement PlotListener.onBeforeDraw(...) and do a read lock from within its + // invocation. This is the entry point into that call: + notifyListenersBeforeDraw(canvas); + try { + // need to completely erase what was on the canvas before redrawing, otherwise + // some odd aliasing artifacts begin to build up around the edges of aa'd entities + // over time. + canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); + if (backgroundPaint != null) { + drawBackground(canvas, displayDims.marginatedRect); + } + + layoutManager.draw(canvas); + + if (getBorderPaint() != null) { + drawBorder(canvas, displayDims.marginatedRect); + } + } catch (Exception e) { + Log.e(TAG, "Exception while rendering Plot.", e); + } + + isIdle = true; + // any series interested in synchronizing with plot should + // implement PlotListener.onAfterDraw(...) and do a read unlock from within that + // invocation. This is the entry point for that invocation. + notifyListenersAfterDraw(canvas); + } finally { + } + } + + + /** + * Sets the visual style of the plot's border. + * @param style + * @param radiusX Sets the X radius for BorderStyle.ROUNDED. Use null for all other styles. + * @param radiusY Sets the Y radius for BorderStyle.ROUNDED. Use null for all other styles. + */ + public void setBorderStyle(BorderStyle style, Float radiusX, Float radiusY) { + if (style == Plot.BorderStyle.ROUNDED) { + if (radiusX == null || radiusY == null){ + throw new IllegalArgumentException("radiusX and radiusY cannot be null when using BorderStyle.ROUNDED"); + } + this.borderRadiusX = radiusX; + this.borderRadiusY = radiusY; + } + this.borderStyle = style; + } + + /** + * Draws the plot's outer border. + * @param canvas + */ + protected void drawBorder(Canvas canvas, RectF dims) { + drawRect(canvas, dims, borderPaint); + } + + protected void drawBackground(Canvas canvas, RectF dims) { + drawRect(canvas, dims, backgroundPaint); + } + + protected void drawRect(Canvas canvas, RectF dims, Paint paint) { + switch (borderStyle) { + case ROUNDED: + canvas.drawRoundRect(dims, borderRadiusX, borderRadiusY, paint); + break; + case SQUARE: + default: + canvas.drawRect(dims, paint); + break; + } + } + + public LayoutManager getLayoutManager() { + return layoutManager; + } + + public void setLayoutManager(LayoutManager layoutManager) { + this.layoutManager = layoutManager; + } + + public Paint getBackgroundPaint() { + return backgroundPaint; + } + + public void setBackgroundPaint(Paint backgroundPaint) { + this.backgroundPaint = backgroundPaint; + } + + /** + * Convenience method - wraps the individual setMarginXXX methods into a single method. + * @param left + * @param top + * @param right + * @param bottom + */ + public void setPlotMargins(float left, float top, float right, float bottom) { + setPlotMarginLeft(left); + setPlotMarginTop(top); + setPlotMarginRight(right); + setPlotMarginBottom(bottom); + } + + /** + * Convenience method - wraps the individual setPaddingXXX methods into a single method. + * @param left + * @param top + * @param right + * @param bottom + */ + public void setPlotPadding(float left, float top, float right, float bottom) { + setPlotPaddingLeft(left); + setPlotPaddingTop(top); + setPlotPaddingRight(right); + setPlotPaddingBottom(bottom); + } + + public float getPlotMarginTop() { + return boxModel.getMarginTop(); + } + + public void setPlotMarginTop(float plotMarginTop) { + boxModel.setMarginTop(plotMarginTop); + } + + public float getPlotMarginBottom() { + return boxModel.getMarginBottom(); + } + + public void setPlotMarginBottom(float plotMarginBottom) { + boxModel.setMarginBottom(plotMarginBottom); + } + + public float getPlotMarginLeft() { + return boxModel.getMarginLeft(); + } + + public void setPlotMarginLeft(float plotMarginLeft) { + boxModel.setMarginLeft(plotMarginLeft); + } + + public float getPlotMarginRight() { + return boxModel.getMarginRight(); + } + + public void setPlotMarginRight(float plotMarginRight) { + boxModel.setMarginRight(plotMarginRight); + } + + public float getPlotPaddingTop() { + return boxModel.getPaddingTop(); + } + + public void setPlotPaddingTop(float plotPaddingTop) { + boxModel.setPaddingTop(plotPaddingTop); + } + + public float getPlotPaddingBottom() { + return boxModel.getPaddingBottom(); + } + + public void setPlotPaddingBottom(float plotPaddingBottom) { + boxModel.setPaddingBottom(plotPaddingBottom); + } + + public float getPlotPaddingLeft() { + return boxModel.getPaddingLeft(); + } + + public void setPlotPaddingLeft(float plotPaddingLeft) { + boxModel.setPaddingLeft(plotPaddingLeft); + } + + public float getPlotPaddingRight() { + return boxModel.getPaddingRight(); + } + + public void setPlotPaddingRight(float plotPaddingRight) { + boxModel.setPaddingRight(plotPaddingRight); + } + + public Paint getBorderPaint() { + return borderPaint; + } + + /** + * Set's the paint used to draw the border. Note that this method + * copies borderPaint and set's the copy's Paint.Style attribute to + * Paint.Style.STROKE. + * @param borderPaint + */ + public void setBorderPaint(Paint borderPaint) { + if(borderPaint == null) { + this.borderPaint = null; + } else { + this.borderPaint = new Paint(borderPaint); + this.borderPaint.setStyle(Paint.Style.STROKE); + } + } +} diff --git a/androidplot-core/src/main/java/com/androidplot/exception/PlotRenderException.java b/androidplot-core/src/main/java/com/androidplot/exception/PlotRenderException.java deleted file mode 100644 index 62a6e6d9..00000000 --- a/androidplot-core/src/main/java/com/androidplot/exception/PlotRenderException.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2015 AndroidPlot.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.androidplot.exception; - -public class PlotRenderException extends Exception { - public PlotRenderException(String message) { - super(message); - } -} diff --git a/androidplot-core/src/main/java/com/androidplot/pie/PieRenderer.java b/androidplot-core/src/main/java/com/androidplot/pie/PieRenderer.java index caaddc06..db8921dc 100644 --- a/androidplot-core/src/main/java/com/androidplot/pie/PieRenderer.java +++ b/androidplot-core/src/main/java/com/androidplot/pie/PieRenderer.java @@ -18,7 +18,6 @@ import android.graphics.*; -import com.androidplot.exception.PlotRenderException; import com.androidplot.ui.SeriesBundle; import com.androidplot.ui.SeriesRenderer; import com.androidplot.ui.RenderStack; @@ -58,7 +57,7 @@ public float getRadius(RectF rect) { @Override public void onRender(Canvas canvas, RectF plotArea, Segment series, SegmentFormatter formatter, - RenderStack stack) throws PlotRenderException { + RenderStack stack) { // This renderer renders all series in one shot, so exclude any remaining series // from causing subsequent invocations of onRender: diff --git a/androidplot-core/src/main/java/com/androidplot/pie/PieWidget.java b/androidplot-core/src/main/java/com/androidplot/pie/PieWidget.java index 38c23b27..1b7b2a80 100644 --- a/androidplot-core/src/main/java/com/androidplot/pie/PieWidget.java +++ b/androidplot-core/src/main/java/com/androidplot/pie/PieWidget.java @@ -17,7 +17,6 @@ package com.androidplot.pie; import android.graphics.*; -import com.androidplot.exception.PlotRenderException; import com.androidplot.ui.LayoutManager; import com.androidplot.ui.Size; import com.androidplot.ui.widget.Widget; @@ -38,7 +37,7 @@ public PieWidget(LayoutManager layoutManager, PieChart pieChart, Size metrics) { } @Override - protected void doOnDraw(Canvas canvas, RectF widgetRect) throws PlotRenderException { + protected void doOnDraw(Canvas canvas, RectF widgetRect) { renderStack.sync(); for(RenderStack.StackElement thisElement : renderStack.getElements()) { if(thisElement.isEnabled()) { diff --git a/androidplot-core/src/main/java/com/androidplot/ui/LayoutManager.java b/androidplot-core/src/main/java/com/androidplot/ui/LayoutManager.java index 0fb1a04c..45f79a43 100644 --- a/androidplot-core/src/main/java/com/androidplot/ui/LayoutManager.java +++ b/androidplot-core/src/main/java/com/androidplot/ui/LayoutManager.java @@ -25,7 +25,6 @@ import android.view.MotionEvent; import android.view.View; -import com.androidplot.exception.PlotRenderException; import com.androidplot.ui.widget.Widget; import com.androidplot.util.DisplayDimensions; import com.androidplot.util.LinkedLayerList; @@ -83,7 +82,7 @@ public void setMarkupEnabled(boolean enabled) { setDrawOutlineShadowsEnabled(enabled); } - public void draw(Canvas canvas) throws PlotRenderException { + public void draw(Canvas canvas) { if(isDrawMarginsEnabled()) { drawSpacing(canvas, displayDims.canvasRect, displayDims.marginatedRect, marginPaint); } 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 afc1b142..bfedeeb8 100644 --- a/androidplot-core/src/main/java/com/androidplot/ui/SeriesRenderer.java +++ b/androidplot-core/src/main/java/com/androidplot/ui/SeriesRenderer.java @@ -20,7 +20,6 @@ import android.graphics.RectF; import android.graphics.Region; import com.androidplot.Series; -import com.androidplot.exception.PlotRenderException; import com.androidplot.Plot; import java.util.ArrayList; @@ -52,10 +51,9 @@ public SeriesFormatterType getFormatter(SeriesType series) { * @param canvas * @param plotArea * @param sfPair The series / formatter pair to be rendered - * @throws PlotRenderException */ public void render(Canvas canvas, RectF plotArea, SeriesBundle sfPair, RenderStack stack) throws PlotRenderException { + SeriesFormatterType> sfPair, RenderStack stack) { onRender(canvas, plotArea, sfPair.getSeries(), sfPair.getFormatter(), stack); } @@ -67,10 +65,9 @@ public void render(Canvas canvas, RectF plotArea, SeriesBundle items = getLegendItems(); if(legendItemComparator != null) { Collections.sort(items, legendItemComparator); diff --git a/androidplot-core/src/main/java/com/androidplot/ui/widget/Widget.java b/androidplot-core/src/main/java/com/androidplot/ui/widget/Widget.java index d66b85a8..87a43a02 100644 --- a/androidplot-core/src/main/java/com/androidplot/ui/widget/Widget.java +++ b/androidplot-core/src/main/java/com/androidplot/ui/widget/Widget.java @@ -20,7 +20,6 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import com.androidplot.exception.PlotRenderException; import com.androidplot.ui.*; import com.androidplot.util.DisplayDimensions; import com.androidplot.ui.HorizontalPositioning; @@ -372,7 +371,7 @@ protected void onResize(@Nullable RectF oldRect, @NonNull RectF newRect) { // do nothing by default } - public void draw(Canvas canvas) throws PlotRenderException { + public void draw(Canvas canvas) { if (isVisible()) { if (backgroundPaint != null) { drawBackground(canvas, widgetDimensions.canvasRect); @@ -439,7 +438,7 @@ protected void drawBackground(Canvas canvas, RectF widgetRect) { * @param canvas The Canvas to draw onto * @param widgetRect the size and coordinates of this widget */ - protected abstract void doOnDraw(Canvas canvas, RectF widgetRect) throws PlotRenderException; + protected abstract void doOnDraw(Canvas canvas, RectF widgetRect); public Paint getBorderPaint() { return borderPaint; diff --git a/androidplot-core/src/main/java/com/androidplot/xy/AdvancedLineAndPointRenderer.java b/androidplot-core/src/main/java/com/androidplot/xy/AdvancedLineAndPointRenderer.java index ee3e7e24..11b9e8e8 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/AdvancedLineAndPointRenderer.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/AdvancedLineAndPointRenderer.java @@ -18,7 +18,6 @@ import android.content.*; import android.graphics.*; -import com.androidplot.exception.PlotRenderException; import com.androidplot.ui.RenderStack; import com.androidplot.ui.SeriesRenderer; @@ -41,7 +40,7 @@ public AdvancedLineAndPointRenderer(XYPlot plot) { } @Override - protected void onRender(Canvas canvas, RectF plotArea, XYSeries series, Formatter formatter, RenderStack stack) throws PlotRenderException { + protected void onRender(Canvas canvas, RectF plotArea, XYSeries series, Formatter formatter, RenderStack stack) { PointF thisPoint; PointF lastPoint = null; for (int i = 0; i < series.size(); i++) { 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 bb8c4c7f..ced83885 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/BarRenderer.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/BarRenderer.java @@ -51,7 +51,13 @@ public class BarRenderer extends GroupRender public enum BarOrientation { /** - * Bars are drawn "overlapping" one another, with taller bars being drawn behind + * Bars are drawn overlapping one another, in the order their respective series + * was added to the plot. + */ + IN_ORDER, + + /** + * Bars are drawn overlapping one another, with taller bars being drawn behind * the shorter ones. */ OVERLAID, // bars are overlaid in descending y-val order (largest val in back) @@ -187,6 +193,7 @@ public void onRender(Canvas canvas, RectF plotArea, List { + private static final int MARKER_LABEL_SPACING = 2; + public String getText() { return text; } @@ -135,4 +141,37 @@ public PositionMetricType getTextPosition() { public void setTextPosition(PositionMetricType textPosition) { this.textPosition = textPosition; } + + /** + * Renders the text associated with user defined markers + * + * @param canvas + * @param text + * @param gridRect + * @param x + * @param y + */ + protected void drawMarkerText(Canvas canvas, String text, RectF gridRect, + float x, float y) { + if (getText() != null) { + x += MARKER_LABEL_SPACING; + y -= MARKER_LABEL_SPACING; + RectF textRect = new RectF(FontUtils.getStringDimensions(text, getTextPaint() + )); + textRect.offsetTo(x, y - textRect.height()); + + if (textRect.right > gridRect.right) { + textRect.offset(-(textRect.right - gridRect.right), 0); + } + + if (textRect.top < gridRect.top) { + textRect.offset(0, gridRect.top - textRect.top); + } + + canvas.drawText(text, textRect.left, textRect.bottom, getTextPaint() + ); + } + } + + public abstract void draw(Canvas canvas, XYPlot plot, RectF gridRect); } diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XValueMarker.java b/androidplot-core/src/main/java/com/androidplot/xy/XValueMarker.java index af02ca64..a4c12438 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XValueMarker.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XValueMarker.java @@ -16,7 +16,10 @@ package com.androidplot.xy; +import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.RectF; + import com.androidplot.ui.VerticalPositioning; import com.androidplot.ui.VerticalPosition; @@ -55,4 +58,19 @@ public XValueMarker(Number value, String text, VerticalPosition textPosition, Pa public XValueMarker(Number value, String text, VerticalPosition textPosition, int linePaint, int textPaint) { super(value, text, textPosition, linePaint, textPaint); } + + @Override + public void draw(Canvas canvas, XYPlot plot, RectF gridRect) { + if (getValue() != null) { + float xPix = (float) plot.getBounds().xRegion + .transform(getValue().doubleValue(), gridRect.left, gridRect.right, false); + canvas.drawLine(xPix, gridRect.top, xPix, gridRect.bottom, getLinePaint() + ); + float yPix = getTextPosition().getPixelValue(gridRect.height()); + yPix += gridRect.top; + if (getText() != null) { + drawMarkerText(canvas, getText(), gridRect, xPix, yPix); + } + } + } } 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 22c551e9..af1f2729 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java @@ -27,7 +27,6 @@ import com.androidplot.R; import com.androidplot.Region; -import com.androidplot.exception.PlotRenderException; import com.androidplot.ui.Insets; import com.androidplot.ui.LayoutManager; import com.androidplot.ui.RenderStack; @@ -63,8 +62,6 @@ public class XYGraphWidget extends Widget { private static final float DEFAULT_LINE_LABEL_TEXT_SIZE_PX = PixelUtils.spToPix(15); - private static final int MARKER_LABEL_SPACING = TWO; - /** * Line interval per range label */ @@ -542,8 +539,7 @@ protected void recalculateSizes(@Nullable RectF rect) { } @Override - protected void doOnDraw(Canvas canvas, RectF widgetRect) - throws PlotRenderException { + protected void doOnDraw(Canvas canvas, RectF widgetRect) { // don't draw if we have no space to draw into if (gridRect.height() > ZERO && gridRect.width() > ZERO) { @@ -692,74 +688,16 @@ protected void drawGrid(Canvas canvas) { } } - /** - * Renders the text associated with user defined markers - * - * @param canvas - * @param text - * @param marker - * @param x - * @param y - */ - protected void drawMarkerText(Canvas canvas, String text, ValueMarker marker, - float x, float y) { - if (marker.getText() != null) { - 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 > gridRect.right) { - textRect.offset(-(textRect.right - gridRect.right), ZERO); - } - - if (textRect.top < gridRect.top) { - textRect.offset(0, gridRect.top - textRect.top); - } - - canvas.drawText(text, textRect.left, textRect.bottom, - marker.getTextPaint() - ); - } - } - protected void drawMarkers(Canvas canvas) { if (plot.getYValueMarkers() != null && plot.getYValueMarkers().size() > 0) { for (YValueMarker marker : plot.getYValueMarkers()) { - if (marker.getValue() != null) { - float yPix = (float) plot.getBounds().yRegion - .transform(marker.getValue() - .doubleValue(), gridRect.top, gridRect.bottom, true); - canvas.drawLine(gridRect.left, yPix, - gridRect.right, yPix, marker.getLinePaint() - ); - - float xPix = marker.getTextPosition().getPixelValue( - gridRect.width()); - xPix += gridRect.left; - drawMarkerText(canvas, marker.getText(), marker, xPix, yPix); - } + marker.draw(canvas, plot, gridRect); } } if (plot.getXValueMarkers() != null && plot.getXValueMarkers().size() > 0) { for (XValueMarker marker : plot.getXValueMarkers()) { - if (marker.getValue() != null) { - float xPix = (float) plot.getBounds().xRegion - .transform(marker.getValue() - .doubleValue(), gridRect.left, gridRect.right, false); - canvas.drawLine(xPix, gridRect.top, xPix, gridRect.bottom, - marker.getLinePaint() - ); - float yPix = marker.getTextPosition().getPixelValue(gridRect.height()); - yPix += gridRect.top; - if (marker.getText() != null) { - drawMarkerText(canvas, marker.getText(), marker, xPix, yPix); - } - } + marker.draw(canvas, plot, gridRect); } } } @@ -839,9 +777,8 @@ protected void drawGridBackground(Canvas canvas) { * Draws lines and points for each element in the series. * * @param canvas - * @throws PlotRenderException */ - protected void drawData(Canvas canvas) throws PlotRenderException { + protected void drawData(Canvas canvas) { if (drawGridOnTop) { drawGridBackground(canvas); } diff --git a/androidplot-core/src/main/java/com/androidplot/xy/YValueMarker.java b/androidplot-core/src/main/java/com/androidplot/xy/YValueMarker.java index f76dadd5..0e4aa435 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/YValueMarker.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/YValueMarker.java @@ -16,7 +16,10 @@ package com.androidplot.xy; +import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.RectF; + import com.androidplot.ui.HorizontalPositioning; import com.androidplot.ui.HorizontalPosition; @@ -55,4 +58,21 @@ public YValueMarker(Number value, String text, HorizontalPosition textPosition, public YValueMarker(Number value, String text, HorizontalPosition textPosition, int linePaint, int textPaint) { super(value, text, textPosition, linePaint, textPaint); } + + @Override + public void draw(Canvas canvas, XYPlot plot, RectF gridRect) { + if (getValue() != null) { + float yPix = (float) plot.getBounds().yRegion + .transform(getValue() + .doubleValue(), gridRect.top, gridRect.bottom, true); + canvas.drawLine(gridRect.left, yPix, + gridRect.right, yPix, getLinePaint() + ); + + float xPix = getTextPosition().getPixelValue( + gridRect.width()); + xPix += gridRect.left; + drawMarkerText(canvas, getText(), gridRect, xPix, yPix); + } + } } diff --git a/androidplot-core/src/test/java/com/androidplot/PlotTest.java b/androidplot-core/src/test/java/com/androidplot/PlotTest.java index dc5e5e88..5a750afd 100644 --- a/androidplot-core/src/test/java/com/androidplot/PlotTest.java +++ b/androidplot-core/src/test/java/com/androidplot/PlotTest.java @@ -20,11 +20,8 @@ import android.graphics.*; import android.util.*; -import com.androidplot.exception.PlotRenderException; import com.androidplot.test.*; import com.androidplot.ui.*; -import com.androidplot.xy.LineAndPointFormatter; -import com.androidplot.xy.SimpleXYSeries; import com.halfhp.fig.*; import org.junit.Test; import org.mockito.Mock; @@ -36,7 +33,6 @@ import static junit.framework.Assert.assertNotSame; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -47,7 +43,7 @@ public class PlotTest extends AndroidplotTest { SeriesRegistry mockSeriesRegistry; @Test - public void testInit_withoutAttrs() throws Exception { + public void testInit_withoutAttrs() { Plot plot = mock(Plot.class); plot.init(RuntimeEnvironment.application, null, 0); @@ -56,7 +52,7 @@ public void testInit_withoutAttrs() throws Exception { } @Test - public void testInit_withAttrs() throws Exception { + public void testInit_withAttrs() { Plot plot = new MockPlot("MockPlot"); AttributeSet attrs = mock(AttributeSet.class); plot.init(RuntimeEnvironment.application, attrs, 0); @@ -64,7 +60,7 @@ public void testInit_withAttrs() throws Exception { } @Test - public void testAddSeries() throws Exception { + public void testAddSeries() { Plot plot = new MockPlot("MockPlot"); MockSeries m1 = new MockSeries(); @@ -97,7 +93,7 @@ public void testAddSeries() throws Exception { } @Test - public void testRemoveSeries() throws Exception { + public void testRemoveSeries() { Plot plot = new MockPlot("MockPlot"); @@ -157,7 +153,7 @@ public void testRemoveSeries() throws Exception { @Test - public void testGetFormatter() throws Exception { + public void testGetFormatter() { Plot plot = new MockPlot("MockPlot"); MockSeries m1 = new MockSeries(); @@ -185,7 +181,7 @@ public void testGetFormatter() throws Exception { } @Test - public void testGetRendererList() throws Exception { + public void testGetRendererList() { Plot plot = new MockPlot("MockPlot"); @@ -206,7 +202,7 @@ public void testGetRendererList() throws Exception { } @Test - public void testAddListener() throws Exception { + public void testAddListener() { Plot plot = new MockPlot("MockPlot"); ArrayList listeners = plot.getListeners(); @@ -232,7 +228,7 @@ public void testAddListener() throws Exception { } @Test - public void testRemoveListener() throws Exception { + public void testRemoveListener() { Plot plot = new MockPlot("MockPlot"); ArrayList listeners = plot.getListeners(); @@ -379,7 +375,7 @@ public MockRenderer1(Plot plot) { } @Override - public void onRender(Canvas canvas, RectF plotArea, Series series, Formatter formatter, RenderStack stack) throws PlotRenderException { + public void onRender(Canvas canvas, RectF plotArea, Series series, Formatter formatter, RenderStack stack) { } @@ -396,7 +392,7 @@ public MockRenderer2(Plot plot) { } @Override - public void onRender(Canvas canvas, RectF plotArea, Series series, Formatter formatter, RenderStack stack) throws PlotRenderException { + public void onRender(Canvas canvas, RectF plotArea, Series series, Formatter formatter, RenderStack stack) { } diff --git a/androidplot-core/src/test/java/com/androidplot/test/TestUtils.java b/androidplot-core/src/test/java/com/androidplot/test/TestUtils.java index ecb78333..1f8f1457 100644 --- a/androidplot-core/src/test/java/com/androidplot/test/TestUtils.java +++ b/androidplot-core/src/test/java/com/androidplot/test/TestUtils.java @@ -56,11 +56,11 @@ public static XYSeries generateXYSeriesWithNulls(String title, int size) { */ @SuppressLint("NewApi") public static MotionEvent newPointerDownEvent(int finger1x, int finger1y, int finger2x, int finger2y) { - MotionEvent me = MotionEvent.obtain(0, 0, 0, 0, 0, 0); + MotionEvent me = MotionEvent.obtain(0, 0, MotionEvent.ACTION_POINTER_DOWN, finger1x, finger1y, 0); ShadowMotionEvent sme = shadowOf(me); - sme.setAction(MotionEvent.ACTION_POINTER_DOWN); + //sme.setAction(MotionEvent.ACTION_POINTER_DOWN); sme.setPointerIds(0, 1); - sme.setLocation(finger1x, finger1y); + //sme.setLocation(finger1x, finger1y); sme.setPointer2(finger2x, finger2y); return me; } diff --git a/androidplot-core/src/test/java/com/androidplot/ui/widget/WidgetTest.java b/androidplot-core/src/test/java/com/androidplot/ui/widget/WidgetTest.java index db81d57c..408edc33 100644 --- a/androidplot-core/src/test/java/com/androidplot/ui/widget/WidgetTest.java +++ b/androidplot-core/src/test/java/com/androidplot/ui/widget/WidgetTest.java @@ -4,7 +4,6 @@ import android.graphics.RectF; import android.support.annotation.NonNull; -import com.androidplot.exception.PlotRenderException; import com.androidplot.test.AndroidplotTest; import com.androidplot.ui.Anchor; import com.androidplot.ui.HorizontalPositioning; @@ -22,6 +21,8 @@ import org.mockito.Mockito; import static junit.framework.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.spy; @@ -88,14 +89,14 @@ public void position_withAnchor_updatesPositionWithNewAnchor() { } @Test - public void draw_sizeChanged_invokesOnResizeBeforeDoOnDraw() throws Exception { + public void draw_sizeChanged_invokesOnResizeBeforeDoOnDraw() { InOrder inOrder = Mockito.inOrder(widget); widget.draw(canvas); - inOrder.verify(widget).onResize(any(RectF.class), any(RectF.class)); + inOrder.verify(widget).onResize(isNull(RectF.class), any(RectF.class)); inOrder.verify(widget).doOnDraw(eq(canvas), any(RectF.class)); - verify(widget).onResize(any(RectF.class), any(RectF.class)); + verify(widget).onResize(isNull(RectF.class), any(RectF.class)); } static class TestWidget extends Widget { @@ -105,7 +106,7 @@ public TestWidget(@NonNull LayoutManager layoutManager, @NonNull Size size) { } @Override - protected void doOnDraw(Canvas canvas, RectF widgetRect) throws PlotRenderException { + protected void doOnDraw(Canvas canvas, RectF widgetRect) { // nothing to do } } diff --git a/androidplot-core/src/test/java/com/androidplot/xy/BarRendererTest.java b/androidplot-core/src/test/java/com/androidplot/xy/BarRendererTest.java index 2bb317b5..e4624d08 100644 --- a/androidplot-core/src/test/java/com/androidplot/xy/BarRendererTest.java +++ b/androidplot-core/src/test/java/com/androidplot/xy/BarRendererTest.java @@ -61,14 +61,14 @@ public class BarRendererTest extends AndroidplotTest { ArgumentCaptor rectCaptor; @Before - public void setUp() throws Exception { + public void setUp() { canvas = spy(new Canvas()); xyPlot = spy(new XYPlot(getContext(), "My Plot")); barFormatter = spy(new BarFormatter(Color.RED, Color.RED)); } @Test - public void testOnRender_handlesNullValues() throws Exception { + public void onRender_handlesNullValues() { XYSeries s1 = new SimpleXYSeries(SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "s1", null, 5, null); XYSeries s2 = new SimpleXYSeries(SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "s2", null, 5, null); @@ -83,7 +83,7 @@ public void testOnRender_handlesNullValues() throws Exception { } @Test - public void testOnRender_stacked() throws Exception { + public void onRender_stacked() { XYSeries s1 = new SimpleXYSeries(SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "s1", 2, 5, 7); XYSeries s2 = new SimpleXYSeries(SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "s2", 8, 5, 3); @@ -119,7 +119,7 @@ public void testOnRender_stacked() throws Exception { } @Test - public void testOnRender_sideBySide() throws Exception { + public void onRender_sideBySide() { XYSeries s1 = new SimpleXYSeries(SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "s1", 0, 5, 10); XYSeries s2 = new SimpleXYSeries(SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "s2", 1, 7.5, 10); @@ -150,7 +150,35 @@ public void testOnRender_sideBySide() throws Exception { } @Test - public void testOnRender_overlaid() throws Exception { + public void onRender_inOrder_drawsFirstAddedSeriesFirst() { + XYSeries s1 = new SimpleXYSeries(SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "s1", 1, 5, 6); + XYSeries s2 = new SimpleXYSeries(SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "s2", 2, 4, 7); + + BarRenderer renderer = setupRendererForTesting(s1, s2); + renderer.setBarOrientation(BarRenderer.BarOrientation.IN_ORDER); + + xyPlot.setUserRangeOrigin(0); + xyPlot.calculateMinMaxVals(); + renderer.onRender(canvas, plotArea, s1, barFormatter, renderStack); + + verify(renderer, times(6)) + .drawBar(eq(canvas), barCaptor.capture(), rectCaptor.capture()); + + // list of all bars drawn, in the exact order they were drawn. + List bars = barCaptor.getAllValues(); + + assertEquals(bars.get(0).getY(), s1.getY(0)); + assertEquals(bars.get(1).getY(), s2.getY(0)); + + assertEquals(bars.get(2).getY(), s1.getY(1)); + assertEquals(bars.get(3).getY(), s2.getY(1)); + + assertEquals(bars.get(4).getY(), s1.getY(2)); + assertEquals(bars.get(5).getY(), s2.getY(2)); + } + + @Test + public void onRender_overlaid_drawsBarsWithExpectedHeight() { XYSeries s1 = new SimpleXYSeries(SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "s1", 0, 5, 10); XYSeries s2 = new SimpleXYSeries(SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "s2", 1, 7.5, 10); @@ -183,10 +211,9 @@ public void testOnRender_overlaid() throws Exception { /** * Verify that positive values are drawn in order of highest yVal, while negative values are * drawn in order of lowest yVal. - * @throws Exception */ @Test - public void testOnRender_overlaid_drawsBarsInOrder() throws Exception { + public void onRender_overlaid_drawsSmallestBarsLast() { XYSeries s1 = new SimpleXYSeries(SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "s1", -1, -2, 1, 2); XYSeries s2 = new SimpleXYSeries(SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "s2", -2, -1, 2, 1); @@ -211,7 +238,7 @@ public void testOnRender_overlaid_drawsBarsInOrder() throws Exception { } @Test - public void testFixedBarWidth() throws Exception { + public void onRender_fixedBarWidth_rendersAllBarsWithSameWidth() { XYSeries s1 = new SimpleXYSeries(SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "s1", 0, 5, 10); XYSeries s2 = new SimpleXYSeries(SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "s2", 1, 7.5, 10); @@ -233,7 +260,7 @@ public void testFixedBarWidth() throws Exception { } @Test - public void testFixedGapWidth() throws Exception { + public void onRender_fixedGapWidth_rendersFixedGapBetweenAllBars() { XYSeries s1 = new SimpleXYSeries(SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "s1", 0, 5, 10); XYSeries s2 = new SimpleXYSeries(SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "s2", 1, 7.5, 10); diff --git a/androidplot-core/src/test/java/com/androidplot/xy/XYGraphWidgetTest.java b/androidplot-core/src/test/java/com/androidplot/xy/XYGraphWidgetTest.java index 65d3898a..03897972 100644 --- a/androidplot-core/src/test/java/com/androidplot/xy/XYGraphWidgetTest.java +++ b/androidplot-core/src/test/java/com/androidplot/xy/XYGraphWidgetTest.java @@ -30,6 +30,7 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; +import static org.mockito.ArgumentMatchers.anyDouble; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyFloat; @@ -323,9 +324,9 @@ private void runDrawGridTest() { // expecting a 100x100 grid to be drawn: verify(graphWidget, times(100)) - .drawDomainLine(eq(canvas), anyFloat(), anyFloat(), any(Paint.class), eq(false)); + .drawDomainLine(eq(canvas), anyFloat(), anyDouble(), any(Paint.class), eq(false)); verify(graphWidget, times(100)) - .drawRangeLine(eq(canvas), anyFloat(), anyFloat(), any(Paint.class), eq(false)); + .drawRangeLine(eq(canvas), anyFloat(), anyDouble(), any(Paint.class), eq(false)); } } diff --git a/build.gradle b/build.gradle index 1e1d8c9f..41312fc7 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ ext { theTargetSdkVersion = 28 theMinSdkVersion = 5 theVersionName = '1.5.7' - theVersionCode = 0 + theVersionCode = 1 } buildscript { @@ -38,11 +38,10 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.1.1' + classpath 'com.android.tools.build:gradle:3.4.2' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.0' - classpath 'com.vanniktech:gradle-android-junit-jacoco-plugin:0.6.0' - classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.6.3' + classpath 'com.dicedmelon.gradle:jacoco-android:0.1.4' } } @@ -52,7 +51,3 @@ allprojects { google() } } - -task wrapper(type: Wrapper) { - gradleVersion = '2.14.1' -} diff --git a/demoapp/build.gradle b/demoapp/build.gradle index 36a7c800..e0e1d1c2 100644 --- a/demoapp/build.gradle +++ b/demoapp/build.gradle @@ -18,12 +18,12 @@ apply plugin: 'com.android.application' apply plugin: 'com.github.triplet.play' dependencies { - compile project(':androidplot-core') - compile 'com.crittercism:crittercism-android-agent:5.4.0' + implementation project(':androidplot-core') + implementation 'com.crittercism:crittercism-android-agent:5.4.0' - debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4' - releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4' - testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4' + debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3' + releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3' + testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3' } buildscript { diff --git a/docs/quickstart.md b/docs/quickstart.md index ffc3827d..08abc2dd 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -14,7 +14,7 @@ To use the library in your gradle project add the following to your build.gradle ```groovy dependencies { - compile "com.androidplot:androidplot-core:1.5.6" + compile "com.androidplot:androidplot-core:1.5.7" } ``` diff --git a/docs/release_notes.md b/docs/release_notes.md index 6bebeb8c..b67acaff 100644 --- a/docs/release_notes.md +++ b/docs/release_notes.md @@ -2,8 +2,15 @@ For details on what to expect in general when updating to a new version of Androiplot, check out the [versioning doc](versioning.md). -# 1.5.6 +# 1.5.7 +* (#94) Potential fix / better error logging for a crash caused by a buffered canvas resize with illegal arguments. +* (#93) Fix Android 9 compiler warnings. +* (#83) Fix NPE when attempting to recycle an already null buffered canvas instance. +* Remove unused `PlotRenderException`. +* Added `IN_ORDER` BarRenderer mode. + +# 1.5.6 * Adds convenience methods for saving / restoring `PanZoom` state. * (#80) Targets SDK 28, fixing compatibility issues. diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 42a64d0b..4c7210e5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Dec 05 07:26:55 CST 2017 +#Fri Jul 19 09:24:31 CDT 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip From 8ad3fca2e396ad77f13a3d5c134a449f966b38d7 Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Sat, 20 Jul 2019 12:11:12 -0500 Subject: [PATCH 02/30] fix play deploy --- demoapp/build.gradle | 31 ++++++++++++++---------- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/demoapp/build.gradle b/demoapp/build.gradle index e0e1d1c2..191280a5 100644 --- a/demoapp/build.gradle +++ b/demoapp/build.gradle @@ -14,17 +14,8 @@ * limitations under the License. */ -apply plugin: 'com.android.application' -apply plugin: 'com.github.triplet.play' - -dependencies { - implementation project(':androidplot-core') - implementation 'com.crittercism:crittercism-android-agent:5.4.0' - - debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3' - releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3' - testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3' -} +//apply plugin: 'com.android.application' +//apply plugin: 'com.github.triplet.play' buildscript { @@ -32,10 +23,24 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.github.triplet.gradle:play-publisher:1.2.0' + classpath 'com.github.triplet.gradle:play-publisher:2.2.1' } } +plugins { + id 'com.android.application' + id 'com.github.triplet.play' version '2.2.1' +} + +dependencies { + implementation project(':androidplot-core') + implementation 'com.crittercism:crittercism-android-agent:5.4.0' + + debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3' + releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3' + testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3' +} + android { compileSdkVersion theCompileSdkVersion @@ -78,6 +83,6 @@ android { play { // see: https://github.com/Triple-T/gradle-play-publisher - jsonFile = file(System.getenv("PUBLISHER_ACCT_JSON_FILE") ?: "publisher.json") + serviceAccountCredentials = file(System.getenv("PUBLISHER_ACCT_JSON_FILE") ?: "publisher.json") track = 'beta' } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4c7210e5..b37f1ae6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip From 3c1da1a5dab6c9f198624b7e894c5a8eec66311a Mon Sep 17 00:00:00 2001 From: JackJackE <37071007+mtjin@users.noreply.github.com> Date: Thu, 26 Dec 2019 00:33:31 +0900 Subject: [PATCH 03/30] upgrade gradle version and refactoring code in a line that doesn't affect the program (#99) --- androidplot-core/build.gradle | 4 ++-- build.gradle | 4 ++-- .../demos/BarPlotExampleActivity.java | 2 ++ .../androidplot/demos/DynamicXYPlotActivity.java | 16 ++++++++-------- .../java/com/androidplot/demos/ECGExample.java | 6 +++--- .../com/androidplot/demos/ListViewActivity.java | 8 ++++---- .../demos/SimplePieChartActivity.java | 2 ++ .../androidplot/demos/TimeSeriesActivity.java | 2 ++ 8 files changed, 25 insertions(+), 19 deletions(-) diff --git a/androidplot-core/build.gradle b/androidplot-core/build.gradle index 0592389b..e513a542 100644 --- a/androidplot-core/build.gradle +++ b/androidplot-core/build.gradle @@ -92,9 +92,9 @@ dependencies { implementation 'com.halfhp.fig:figlib:1.0.7' implementation 'com.android.support:support-annotations:28.0.0' - testImplementation "org.mockito:mockito-core:2.13.0" + testImplementation "org.mockito:mockito-core:3.0.0" testImplementation group: 'junit', name: 'junit', version: '4.12' - testImplementation "org.robolectric:robolectric:4.0.1" + testImplementation "org.robolectric:robolectric:4.2.1" // temp fix for: // https://github.com/robolectric/robolectric/issues/1932 diff --git a/build.gradle b/build.gradle index 41312fc7..a00c8e72 100644 --- a/build.gradle +++ b/build.gradle @@ -38,9 +38,9 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.4.2' + classpath 'com.android.tools.build:gradle:3.5.3' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.0' + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' classpath 'com.dicedmelon.gradle:jacoco-android:0.1.4' } } diff --git a/demoapp/src/main/java/com/androidplot/demos/BarPlotExampleActivity.java b/demoapp/src/main/java/com/androidplot/demos/BarPlotExampleActivity.java index 8d8720d1..3cc2faa4 100644 --- a/demoapp/src/main/java/com/androidplot/demos/BarPlotExampleActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/BarPlotExampleActivity.java @@ -22,6 +22,7 @@ import java.text.ParsePosition; import java.util.Arrays; +import android.annotation.SuppressLint; import android.app.Activity; import android.graphics.Color; import android.graphics.Paint; @@ -86,6 +87,7 @@ private enum SeriesSize { private Pair selection; + @SuppressLint("ClickableViewAccessibility") @Override public void onCreate(Bundle savedInstanceState) { diff --git a/demoapp/src/main/java/com/androidplot/demos/DynamicXYPlotActivity.java b/demoapp/src/main/java/com/androidplot/demos/DynamicXYPlotActivity.java index f76dca01..f18b518b 100644 --- a/demoapp/src/main/java/com/androidplot/demos/DynamicXYPlotActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/DynamicXYPlotActivity.java @@ -138,8 +138,8 @@ public void notifyObservers() { private static final int MAX_AMP_SEED = 100; private static final int MIN_AMP_SEED = 10; private static final int AMP_STEP = 1; - public static final int SINE1 = 0; - public static final int SINE2 = 1; + static final int SINE1 = 0; + static final int SINE2 = 1; private static final int SAMPLE_SIZE = 31; private int phase = 0; private int sinAmp = 1; @@ -150,7 +150,7 @@ public void notifyObservers() { notifier = new MyObservable(); } - public void stopThread() { + void stopThread() { keepRunning = false; } @@ -181,18 +181,18 @@ public void run() { } } - public int getItemCount(int series) { + int getItemCount(int series) { return SAMPLE_SIZE; } - public Number getX(int series, int index) { + Number getX(int series, int index) { if (index >= SAMPLE_SIZE) { throw new IllegalArgumentException(); } return index; } - public Number getY(int series, int index) { + Number getY(int series, int index) { if (index >= SAMPLE_SIZE) { throw new IllegalArgumentException(); } @@ -208,7 +208,7 @@ public Number getY(int series, int index) { } } - public void addObserver(Observer observer) { + void addObserver(Observer observer) { notifier.addObserver(observer); } @@ -223,7 +223,7 @@ class SampleDynamicSeries implements XYSeries { private int seriesIndex; private String title; - public SampleDynamicSeries(SampleDynamicXYDatasource datasource, int seriesIndex, String title) { + SampleDynamicSeries(SampleDynamicXYDatasource datasource, int seriesIndex, String title) { this.datasource = datasource; this.seriesIndex = seriesIndex; this.title = title; diff --git a/demoapp/src/main/java/com/androidplot/demos/ECGExample.java b/demoapp/src/main/java/com/androidplot/demos/ECGExample.java index 31d71e87..2bd7dec5 100644 --- a/demoapp/src/main/java/com/androidplot/demos/ECGExample.java +++ b/demoapp/src/main/java/com/androidplot/demos/ECGExample.java @@ -82,7 +82,7 @@ public static class MyFadeFormatter extends AdvancedLineAndPointRenderer.Formatt private int trailSize; - public MyFadeFormatter(int trailSize) { + MyFadeFormatter(int trailSize) { this.trailSize = trailSize; } @@ -125,7 +125,7 @@ public static class ECGModel implements XYSeries { * @param size Sample size contained within this model * @param updateFreqHz Frequency at which new samples are added to the model */ - public ECGModel(int size, int updateFreqHz) { + ECGModel(int size, int updateFreqHz) { data = new Number[size]; for(int i = 0; i < data.length; i++) { data[i] = 0; @@ -176,7 +176,7 @@ public void run() { }); } - public void start(final WeakReference rendererRef) { + void start(final WeakReference rendererRef) { this.rendererRef = rendererRef; keepRunning = true; thread.start(); diff --git a/demoapp/src/main/java/com/androidplot/demos/ListViewActivity.java b/demoapp/src/main/java/com/androidplot/demos/ListViewActivity.java index 9d640053..590af815 100644 --- a/demoapp/src/main/java/com/androidplot/demos/ListViewActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/ListViewActivity.java @@ -78,10 +78,10 @@ protected void generateData() { double bp = Math.random(); LineAndPointFormatter lpf = new LineAndPointFormatter( - Color.rgb(new Double(rl * 255).intValue(), - new Double(gl * 255).intValue(), new Double(bl * 255).intValue()), - Color.rgb(new Double(rp * 255).intValue(), - new Double(gp * 255).intValue(), new Double(bp * 255).intValue()), + Color.rgb(Double.valueOf(rl * 255).intValue(), + Double.valueOf(gl * 255).intValue(), Double.valueOf(bl * 255).intValue()), + Color.rgb(Double.valueOf(rp * 255).intValue(), + Double.valueOf(gp * 255).intValue(), Double.valueOf(bp * 255).intValue()), null, null); // for fun, configure interpolation on the formatter: diff --git a/demoapp/src/main/java/com/androidplot/demos/SimplePieChartActivity.java b/demoapp/src/main/java/com/androidplot/demos/SimplePieChartActivity.java index 9484176f..cf419083 100644 --- a/demoapp/src/main/java/com/androidplot/demos/SimplePieChartActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/SimplePieChartActivity.java @@ -18,6 +18,7 @@ import android.animation.Animator; import android.animation.ValueAnimator; +import android.annotation.SuppressLint; import android.app.Activity; import android.graphics.*; import android.os.Bundle; @@ -52,6 +53,7 @@ public class SimplePieChartActivity extends Activity private Segment s3; private Segment s4; + @SuppressLint("ClickableViewAccessibility") @Override public void onCreate(Bundle savedInstanceState) { diff --git a/demoapp/src/main/java/com/androidplot/demos/TimeSeriesActivity.java b/demoapp/src/main/java/com/androidplot/demos/TimeSeriesActivity.java index c5811bfc..9a8da790 100644 --- a/demoapp/src/main/java/com/androidplot/demos/TimeSeriesActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/TimeSeriesActivity.java @@ -16,6 +16,7 @@ package com.androidplot.demos; +import android.annotation.SuppressLint; import android.app.Activity; import android.graphics.Color; import android.graphics.DashPathEffect; @@ -102,6 +103,7 @@ public void onCreate(Bundle savedInstanceState) { // create a simple date format that draws on the year portion of our timestamp. // see http://download.oracle.com/javase/1.4.2/docs/api/java/text/SimpleDateFormat.html // for a full description of SimpleDateFormat. + @SuppressLint("SimpleDateFormat") private final SimpleDateFormat dateFormat = new SimpleDateFormat("MMM yyyy"); @Override From b275635276dc9023be0803c2bdc4b2ed516d1053 Mon Sep 17 00:00:00 2001 From: topherbuckley Date: Sun, 19 Sep 2021 02:09:39 +0900 Subject: [PATCH 04/30] Quick start edits (#108) * Changed compile keyword to implementation This is to match updated gradel recommendations * Reordered quickstart guide to avoid unresolved xml file errors Creating the xml files first will remove the unresolved errors you get when copy/pasting the activity code. As people are inherently lazy to read ahead, I think this will eliminate some github issue requests in the future :D --- docs/quickstart.md | 55 +++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/docs/quickstart.md b/docs/quickstart.md index 08abc2dd..18309987 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -14,7 +14,7 @@ To use the library in your gradle project add the following to your build.gradle ```groovy dependencies { - compile "com.androidplot:androidplot-core:1.5.7" + implementation "com.androidplot:androidplot-core:1.5.7" } ``` @@ -55,6 +55,32 @@ directly within your Plot's XML, prefixing each property with "androidPlot". Ex androidPlot.title="My Plot" ``` +Add these files to your **/res/xml** directory: + +#### /res/xml/line_point_formatter_with_labels.xml +```xml + + +``` + +#### /res/xml/line_point_formatter_with_labels_2.xml +```xml + + +``` + # Create an Activity Now let's create an Activity to display the XYPlot we just defined in `simple_xy_plot_example.xml`. The basic steps are: @@ -151,7 +177,6 @@ public class SimpleXYPlotActivity extends Activity { } ``` - One potentially confusing section of the code above are the initializations of LineAndPointFormatter You probably noticed that they take a mysterious reference to an xml resource file. This is actually using [Fig](https://github.com/halfhp/fig) to configure the instance properties from XML. @@ -173,31 +198,5 @@ In general XML configuration should be used over programmatic configuration when more flexibility in terms of defining properties by screen density etc.. For more details on how to programmatically configure Formatters etc. consult the latest Javadocs. -Continuing with the original example above, add these files to your **/res/xml** directory: - -#### /res/xml/line_point_formatter_with_labels.xml -```xml - - -``` - -#### /res/xml/line_point_formatter_with_labels_2.xml -```xml - - -``` - # Whats Next? Learn about [Plot Composition](plot_composition.md) or continue with [XYPlots](xyplot.md). \ No newline at end of file From 6079afc8f18c32efdb9c88b1186cc5300d14f0da Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Sun, 19 Sep 2021 10:59:43 -0500 Subject: [PATCH 05/30] Project updates (#110) --- .circleci/config.yml | 15 +- README.md | 2 +- androidplot-core/build.gradle | 129 +++++++++--------- .../src/main/java/com/androidplot/Plot.java | 3 +- .../com/androidplot/pie/PieLegendItem.java | 2 +- .../com/androidplot/pie/PieLegendWidget.java | 2 +- .../com/androidplot/ui/PositionMetrics.java | 2 +- .../androidplot/ui/widget/LegendWidget.java | 2 +- .../com/androidplot/ui/widget/Widget.java | 4 +- .../java/com/androidplot/util/FastNumber.java | 4 +- .../xy/FastLineAndPointRenderer.java | 2 +- .../xy/FixedSizeEditableXYSeries.java | 4 +- .../main/java/com/androidplot/xy/PanZoom.java | 2 +- .../com/androidplot/xy/XYConstraints.java | 4 +- .../com/androidplot/xy/XYGraphWidget.java | 4 +- .../java/com/androidplot/xy/XYLegendItem.java | 2 +- .../com/androidplot/xy/XYLegendWidget.java | 2 +- .../main/java/com/androidplot/xy/XYPlot.java | 2 +- .../com/androidplot/ui/widget/WidgetTest.java | 2 +- .../com/androidplot/xy/XYGraphWidgetTest.java | 4 +- build.gradle | 23 +--- demoapp-wearable/build.gradle | 8 +- demoapp/build.gradle | 16 +-- .../demos/AnimatedXYPlotActivity.java | 2 +- .../demos/CandlestickChartActivity.java | 2 +- .../androidplot/demos/DualScaleActivity.java | 2 +- .../androidplot/demos/ListViewActivity.java | 2 +- .../demos/SimpleXYPlotActivity.java | 2 +- .../demos/StepChartExampleActivity.java | 2 +- .../androidplot/demos/TimeSeriesActivity.java | 2 +- docs/quickstart.md | 5 +- gradle.properties | 2 + gradle/wrapper/gradle-wrapper.properties | 2 +- 33 files changed, 124 insertions(+), 139 deletions(-) create mode 100644 gradle.properties diff --git a/.circleci/config.yml b/.circleci/config.yml index f333b5c2..bf60104b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,7 +16,7 @@ jobs: # CircleCI maintains a library of pre-built images # documented at https://circleci.com/docs/2.0/circleci-images/ - - image: circleci/android:api-28-alpha + - image: circleci/android:api-30 working_directory: ~/repo @@ -87,17 +87,10 @@ jobs: path: androidplot-core/build/test-results/ - deploy: - name: "Deploy to Bintray" + name: "Publish" command: | - if [ "${CIRCLE_BRANCH}" == "master" ]; - then ./gradlew bintrayUpload; - fi - - - deploy: - name: "Deploy to Google Play" - command: | - if [ "${CIRCLE_BRANCH}" == "master" ]; + if [ "${CIRCLE_BRANCH}" == "project-updates" ]; then ./misc/download_google_publisher_json.sh; - ./gradlew publishApkRelease + ./gradlew publish; fi \ No newline at end of file diff --git a/README.md b/README.md index 7d7020d2..42b3f9ea 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Technical questions should be posted using the [androidplot tag](http://stackove # License Androidplot has been made available under the Apache 2.0 license: - Copyright 2018 Androidplot.com + Copyright 2021 Androidplot.com Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/androidplot-core/build.gradle b/androidplot-core/build.gradle index e513a542..3d255cc2 100644 --- a/androidplot-core/build.gradle +++ b/androidplot-core/build.gradle @@ -14,10 +14,12 @@ * limitations under the License. */ -apply plugin: 'com.android.library' -apply plugin: 'com.github.dcendents.android-maven' -apply plugin: 'com.jfrog.bintray' -apply plugin: 'jacoco-android' +plugins { + id 'com.android.library' + id 'com.dicedmelon.gradle.jacoco-android' + id 'maven-publish' + id 'signing' +} class AttrMarkdown extends DefaultTask { @@ -59,8 +61,6 @@ android { compileSdkVersion theCompileSdkVersion defaultConfig { - versionCode theVersionCode - versionName theVersionName minSdkVersion theMinSdkVersion targetSdkVersion theTargetSdkVersion testApplicationId "com.androidplot.test" @@ -70,14 +70,13 @@ android { unitTests.all { jacoco { includeNoLocationClasses = true + jacoco.excludes = ['jdk.internal.*'] } } } - /** - * TODO: enable and address lint issues. - */ lintOptions { + // TODO: enable and address lint issues. abortOnError false } } @@ -90,15 +89,12 @@ def gitUrl = 'https://github.com/halfhp/androidplot.git' dependencies { - implementation 'com.halfhp.fig:figlib:1.0.7' + implementation 'com.halfhp.fig:figlib:1.0.11' implementation 'com.android.support:support-annotations:28.0.0' - testImplementation "org.mockito:mockito-core:3.0.0" - testImplementation group: 'junit', name: 'junit', version: '4.12' - testImplementation "org.robolectric:robolectric:4.2.1" - // temp fix for: - // https://github.com/robolectric/robolectric/issues/1932 - testImplementation 'org.khronos:opengl-api:gl1.1-android-2.1_r1' + testImplementation "org.mockito:mockito-core:3.9.0" + testImplementation group: 'junit', name: 'junit', version: '4.13.2' + testImplementation "org.robolectric:robolectric:4.5.1" } task javadoc(type: Javadoc) { @@ -123,63 +119,70 @@ task sourcesJar(type: Jar) { from android.sourceSets.main.java.srcDirs } -install { - repositories.mavenInstaller { - pom { - project { - packaging 'aar' - name 'Androidplot Core Library' - description = "Androidplot is a library for creating dynamic and static charts within your Android application." - url siteUrl - licenses { - license { - name 'The Apache Software License, Version 2.0' - url 'http://www.apache.org/licenses/LICENSE-2.0.txt' - } - } - developers { - developer { - id 'halfhp' - name 'Nick Fellows' - email 'halfhp@gmail.com' - } - } - scm { - connection gitUrl - developerConnection gitUrl - url siteUrl +javadoc { + options.overview = "src/main/java/overview.html" +} +afterEvaluate { + publishing { + repositories { + maven { + name = "Maven Central" + url = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" + credentials { + username = System.getenv("OSSRH_ACTOR") + password = System.getenv("OSSRH_TOKEN") } } } - } -} - -bintray { - - // these environment vars must be set when deploying to bintray. - // note: BINTRAY_PASSWORD is actually the api key, but naming it 'password' - // tells the CI environment to obfuscate the value in public logs. - user = System.getenv('BINTRAY_USER') - key = System.getenv('BINTRAY_PASSWORD') + publications { + release(MavenPublication) { + + from components.release + + // You can then customize attributes of the publication as shown below. + groupId = 'com.androidplot' + artifactId = 'androidplot' + version = theVersionName + + pom { + packaging 'aar' + name = 'Androidplot' + description = "Configure any object from XML." + url = gitUrl + licenses { + license { + name = 'The Apache Software License, Version 2.0' + url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' + } + } + developers { + developer { + id = 'halfhp' + name = 'Nick Fellows' + email = 'halfhp@gmail.com' + } + } + scm { + connection = gitUrl + developerConnection = gitUrl + url = gitUrl - configurations = ['archives'] - pkg { - version { - name = theVersionName + } + } + } } - repo = "androidplot" - name = "com.androidplot:androidplot-core" - issueTrackerUrl = "https://github.com/halfhp/androidplot/issues" - websiteUrl = siteUrl - vcsUrl = gitUrl - licenses = ["Apache-2.0"] } } -javadoc { - options.overview = "src/main/java/overview.html" +afterEvaluate { + signing { + def signingKey = System.getenv("SIGNING_KEY") + def signingPassword = System.getenv("SIGNING_PASSWORD") + useInMemoryPgpKeys(signingKey, signingPassword) + sign publishing.publications.release + } } artifacts { diff --git a/androidplot-core/src/main/java/com/androidplot/Plot.java b/androidplot-core/src/main/java/com/androidplot/Plot.java index 47ef0fd6..925a9c78 100644 --- a/androidplot-core/src/main/java/com/androidplot/Plot.java +++ b/androidplot-core/src/main/java/com/androidplot/Plot.java @@ -26,11 +26,12 @@ import android.graphics.RectF; import android.os.Build; import android.os.Looper; -import android.support.annotation.Nullable; import android.util.AttributeSet; import android.util.Log; import android.view.View; +import androidx.annotation.Nullable; + import com.androidplot.ui.Anchor; import com.androidplot.ui.BoxModel; import com.androidplot.ui.Formatter; diff --git a/androidplot-core/src/main/java/com/androidplot/pie/PieLegendItem.java b/androidplot-core/src/main/java/com/androidplot/pie/PieLegendItem.java index 8b4e9913..669b188b 100644 --- a/androidplot-core/src/main/java/com/androidplot/pie/PieLegendItem.java +++ b/androidplot-core/src/main/java/com/androidplot/pie/PieLegendItem.java @@ -1,7 +1,7 @@ package com.androidplot.pie; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.androidplot.ui.widget.LegendItem; diff --git a/androidplot-core/src/main/java/com/androidplot/pie/PieLegendWidget.java b/androidplot-core/src/main/java/com/androidplot/pie/PieLegendWidget.java index c59b7f0b..4d1ccd03 100644 --- a/androidplot-core/src/main/java/com/androidplot/pie/PieLegendWidget.java +++ b/androidplot-core/src/main/java/com/androidplot/pie/PieLegendWidget.java @@ -2,7 +2,7 @@ import android.graphics.Canvas; import android.graphics.RectF; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.androidplot.ui.LayoutManager; import com.androidplot.ui.SeriesBundle; diff --git a/androidplot-core/src/main/java/com/androidplot/ui/PositionMetrics.java b/androidplot-core/src/main/java/com/androidplot/ui/PositionMetrics.java index 88efddcb..377e9e04 100644 --- a/androidplot-core/src/main/java/com/androidplot/ui/PositionMetrics.java +++ b/androidplot-core/src/main/java/com/androidplot/ui/PositionMetrics.java @@ -16,7 +16,7 @@ package com.androidplot.ui; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; public class PositionMetrics implements Comparable { diff --git a/androidplot-core/src/main/java/com/androidplot/ui/widget/LegendWidget.java b/androidplot-core/src/main/java/com/androidplot/ui/widget/LegendWidget.java index e9222888..0da56a3a 100644 --- a/androidplot-core/src/main/java/com/androidplot/ui/widget/LegendWidget.java +++ b/androidplot-core/src/main/java/com/androidplot/ui/widget/LegendWidget.java @@ -4,7 +4,7 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.androidplot.ui.LayoutManager; import com.androidplot.ui.Size; import com.androidplot.ui.TableModel; diff --git a/androidplot-core/src/main/java/com/androidplot/ui/widget/Widget.java b/androidplot-core/src/main/java/com/androidplot/ui/widget/Widget.java index 87a43a02..6d21767b 100644 --- a/androidplot-core/src/main/java/com/androidplot/ui/widget/Widget.java +++ b/androidplot-core/src/main/java/com/androidplot/ui/widget/Widget.java @@ -17,8 +17,8 @@ package com.androidplot.ui.widget; import android.graphics.*; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; +import androidx.annotation.NonNull; import com.androidplot.ui.*; import com.androidplot.util.DisplayDimensions; diff --git a/androidplot-core/src/main/java/com/androidplot/util/FastNumber.java b/androidplot-core/src/main/java/com/androidplot/util/FastNumber.java index 72e8a25d..4fa11d8a 100644 --- a/androidplot-core/src/main/java/com/androidplot/util/FastNumber.java +++ b/androidplot-core/src/main/java/com/androidplot/util/FastNumber.java @@ -1,7 +1,7 @@ package com.androidplot.util; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; /** * An extension of {@link Number} optimized for speed at the cost of memory. diff --git a/androidplot-core/src/main/java/com/androidplot/xy/FastLineAndPointRenderer.java b/androidplot-core/src/main/java/com/androidplot/xy/FastLineAndPointRenderer.java index 5f469ce2..df070325 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/FastLineAndPointRenderer.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/FastLineAndPointRenderer.java @@ -20,7 +20,7 @@ import android.graphics.Paint; import android.graphics.PointF; import android.graphics.RectF; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.androidplot.ui.RenderStack; import com.androidplot.ui.SeriesRenderer; diff --git a/androidplot-core/src/main/java/com/androidplot/xy/FixedSizeEditableXYSeries.java b/androidplot-core/src/main/java/com/androidplot/xy/FixedSizeEditableXYSeries.java index 0e6ea1a1..36ff371e 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/FixedSizeEditableXYSeries.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/FixedSizeEditableXYSeries.java @@ -1,7 +1,7 @@ package com.androidplot.xy; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.androidplot.util.FastNumber; diff --git a/androidplot-core/src/main/java/com/androidplot/xy/PanZoom.java b/androidplot-core/src/main/java/com/androidplot/xy/PanZoom.java index 33d61369..9cd80847 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/PanZoom.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/PanZoom.java @@ -2,7 +2,7 @@ import android.graphics.RectF; import android.graphics.PointF; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.view.*; import com.androidplot.*; diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYConstraints.java b/androidplot-core/src/main/java/com/androidplot/xy/XYConstraints.java index 58338264..c4452d33 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYConstraints.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYConstraints.java @@ -16,8 +16,8 @@ package com.androidplot.xy; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; /** * Calculates the min/max constraints for an xy plane. 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 af1f2729..4f6fa04a 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java @@ -22,8 +22,8 @@ import android.graphics.Paint; import android.graphics.PointF; import android.graphics.RectF; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.androidplot.R; import com.androidplot.Region; diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYLegendItem.java b/androidplot-core/src/main/java/com/androidplot/xy/XYLegendItem.java index 1fd10ebd..51c67159 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYLegendItem.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYLegendItem.java @@ -1,6 +1,6 @@ package com.androidplot.xy; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.androidplot.ui.widget.LegendItem; 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 f08783cb..680fd4b2 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYLegendWidget.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYLegendWidget.java @@ -17,7 +17,7 @@ package com.androidplot.xy; import android.graphics.*; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.androidplot.ui.LayoutManager; import com.androidplot.ui.SeriesBundle; 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 67eaafa8..e519b5a0 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java @@ -22,7 +22,7 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.PointF; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.util.AttributeSet; import com.androidplot.Plot; diff --git a/androidplot-core/src/test/java/com/androidplot/ui/widget/WidgetTest.java b/androidplot-core/src/test/java/com/androidplot/ui/widget/WidgetTest.java index 408edc33..ce398dc9 100644 --- a/androidplot-core/src/test/java/com/androidplot/ui/widget/WidgetTest.java +++ b/androidplot-core/src/test/java/com/androidplot/ui/widget/WidgetTest.java @@ -2,7 +2,7 @@ import android.graphics.Canvas; import android.graphics.RectF; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.androidplot.test.AndroidplotTest; import com.androidplot.ui.Anchor; diff --git a/androidplot-core/src/test/java/com/androidplot/xy/XYGraphWidgetTest.java b/androidplot-core/src/test/java/com/androidplot/xy/XYGraphWidgetTest.java index 03897972..5a0da878 100644 --- a/androidplot-core/src/test/java/com/androidplot/xy/XYGraphWidgetTest.java +++ b/androidplot-core/src/test/java/com/androidplot/xy/XYGraphWidgetTest.java @@ -298,10 +298,10 @@ public void seriesToScreenY_returnsScreenValue() { @Test public void setGridInsets_updatesGridRect() { - graphWidget.setGridInsets(new Insets(0, 0, 0, 0)); + graphWidget.setGridInsets(new com.androidplot.ui.Insets(0, 0, 0, 0)); final RectF oldRect = graphWidget.getGridRect(); - graphWidget.setGridInsets(new Insets(2, 2, 2, 2)); + graphWidget.setGridInsets(new com.androidplot.ui.Insets(2, 2, 2, 2)); final RectF newRect = graphWidget.getGridRect(); assertEquals(oldRect.left + 2, newRect.left); diff --git a/build.gradle b/build.gradle index a00c8e72..96bd5758 100644 --- a/build.gradle +++ b/build.gradle @@ -14,34 +14,25 @@ * limitations under the License. */ -apply plugin: 'java' - -allprojects { - repositories { - jcenter() - } -} - ext { - theCompileSdkVersion = 28 - theTargetSdkVersion = 28 + theCompileSdkVersion = 30 + theTargetSdkVersion = 30 theMinSdkVersion = 5 - theVersionName = '1.5.7' + theVersionName = '1.5.8' theVersionCode = 1 + gitUrl = 'https://github.com/halfhp/androidplot.git' } buildscript { repositories { mavenCentral() - jcenter() + maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots' } google() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.3' - classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' - classpath 'com.dicedmelon.gradle:jacoco-android:0.1.4' + classpath 'com.android.tools.build:gradle:7.0.2' + classpath 'com.dicedmelon.gradle:jacoco-android:0.1.5-SNAPSHOT' } } diff --git a/demoapp-wearable/build.gradle b/demoapp-wearable/build.gradle index 22e5186d..3bfc0ee0 100644 --- a/demoapp-wearable/build.gradle +++ b/demoapp-wearable/build.gradle @@ -16,18 +16,14 @@ buildscript { repositories { - jcenter() google() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.1' + classpath 'com.android.tools.build:gradle:3.1.4' } } -apply plugin: 'com.android.application' -repositories { - jcenter() -} +apply plugin: 'com.android.application' android { compileSdkVersion theCompileSdkVersion diff --git a/demoapp/build.gradle b/demoapp/build.gradle index 191280a5..a5583eb9 100644 --- a/demoapp/build.gradle +++ b/demoapp/build.gradle @@ -14,22 +14,19 @@ * limitations under the License. */ -//apply plugin: 'com.android.application' -//apply plugin: 'com.github.triplet.play' - buildscript { repositories { mavenCentral() } dependencies { - classpath 'com.github.triplet.gradle:play-publisher:2.2.1' + classpath 'com.github.triplet.gradle:play-publisher:3.6.0' } } plugins { id 'com.android.application' - id 'com.github.triplet.play' version '2.2.1' + id 'com.github.triplet.play' version '3.6.0' } dependencies { @@ -37,7 +34,9 @@ dependencies { implementation 'com.crittercism:crittercism-android-agent:5.4.0' debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3' + releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3' + testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3' } @@ -72,17 +71,14 @@ android { } } - - /** - * TODO: uncomment this and address all the lint issues. - */ lintOptions { + // TODO: set to true and address all the lint issues. abortOnError false } } play { // see: https://github.com/Triple-T/gradle-play-publisher - serviceAccountCredentials = file(System.getenv("PUBLISHER_ACCT_JSON_FILE") ?: "publisher.json") + serviceAccountCredentials.set(file(System.getenv("PUBLISHER_ACCT_JSON_FILE") ?: "publisher.json")) track = 'beta' } diff --git a/demoapp/src/main/java/com/androidplot/demos/AnimatedXYPlotActivity.java b/demoapp/src/main/java/com/androidplot/demos/AnimatedXYPlotActivity.java index 75d9553b..43d3adc0 100644 --- a/demoapp/src/main/java/com/androidplot/demos/AnimatedXYPlotActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/AnimatedXYPlotActivity.java @@ -21,7 +21,7 @@ import android.app.Activity; import android.graphics.Color; import android.os.Bundle; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.view.animation.AccelerateDecelerateInterpolator; import com.androidplot.xy.BoundaryMode; diff --git a/demoapp/src/main/java/com/androidplot/demos/CandlestickChartActivity.java b/demoapp/src/main/java/com/androidplot/demos/CandlestickChartActivity.java index 2c37580a..86ae1fbb 100644 --- a/demoapp/src/main/java/com/androidplot/demos/CandlestickChartActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/CandlestickChartActivity.java @@ -21,7 +21,7 @@ import android.graphics.DashPathEffect; import android.graphics.Paint; import android.os.Bundle; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.androidplot.Region; import com.androidplot.util.PixelUtils; diff --git a/demoapp/src/main/java/com/androidplot/demos/DualScaleActivity.java b/demoapp/src/main/java/com/androidplot/demos/DualScaleActivity.java index d164710a..f0116121 100644 --- a/demoapp/src/main/java/com/androidplot/demos/DualScaleActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/DualScaleActivity.java @@ -20,7 +20,7 @@ import android.graphics.Color; import android.graphics.DashPathEffect; import android.os.Bundle; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.androidplot.util.PixelUtils; import com.androidplot.xy.BoundaryMode; diff --git a/demoapp/src/main/java/com/androidplot/demos/ListViewActivity.java b/demoapp/src/main/java/com/androidplot/demos/ListViewActivity.java index 590af815..290b7802 100644 --- a/demoapp/src/main/java/com/androidplot/demos/ListViewActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/ListViewActivity.java @@ -20,7 +20,7 @@ import android.content.Context; import android.graphics.Color; import android.os.Bundle; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java b/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java index 46118949..db3a5a28 100644 --- a/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java @@ -19,7 +19,7 @@ import android.app.Activity; import android.graphics.DashPathEffect; import android.os.Bundle; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.androidplot.util.PixelUtils; import com.androidplot.xy.CatmullRomInterpolator; diff --git a/demoapp/src/main/java/com/androidplot/demos/StepChartExampleActivity.java b/demoapp/src/main/java/com/androidplot/demos/StepChartExampleActivity.java index 79b8272f..4b1fdc35 100644 --- a/demoapp/src/main/java/com/androidplot/demos/StepChartExampleActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/StepChartExampleActivity.java @@ -23,7 +23,7 @@ import android.graphics.Paint; import android.graphics.Shader; import android.os.Bundle; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.androidplot.util.PixelUtils; import com.androidplot.xy.SimpleXYSeries; diff --git a/demoapp/src/main/java/com/androidplot/demos/TimeSeriesActivity.java b/demoapp/src/main/java/com/androidplot/demos/TimeSeriesActivity.java index 9a8da790..12930bd7 100644 --- a/demoapp/src/main/java/com/androidplot/demos/TimeSeriesActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/TimeSeriesActivity.java @@ -22,7 +22,7 @@ import android.graphics.DashPathEffect; import android.graphics.Paint; import android.os.Bundle; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.androidplot.util.PixelUtils; import com.androidplot.xy.BoundaryMode; diff --git a/docs/quickstart.md b/docs/quickstart.md index 18309987..2f1853d2 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -14,10 +14,13 @@ To use the library in your gradle project add the following to your build.gradle ```groovy dependencies { - implementation "com.androidplot:androidplot-core:1.5.7" + implementation "com.androidplot:androidplot-core:1.5.8" } ``` +*NOTE: As of version 1.5.8 Androidplot has migrated over from the Android Support Libraries to androidx. +If you have a very old project and experience issues, may need to stay on version 1.5.7* + If you’re using Proguard obfuscation (Projects created by Android Studio do by default) you’ll also want add this to your proguard-rules.pro file: diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..d015431a --- /dev/null +++ b/gradle.properties @@ -0,0 +1,2 @@ +android.useAndroidX=true +android.enableJetifier=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b37f1ae6..0a24689e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip From 44042626cefab91712dcbb923af2332f6438affe Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Sun, 19 Sep 2021 17:19:54 -0500 Subject: [PATCH 06/30] render mode attrs should have a distinct ordinal value (#111) --- .../src/main/java/com/androidplot/Plot.java | 22 +++++++++---------- .../src/main/res/values/attrs.xml | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/androidplot-core/src/main/java/com/androidplot/Plot.java b/androidplot-core/src/main/java/com/androidplot/Plot.java index 925a9c78..ac2d6a09 100644 --- a/androidplot-core/src/main/java/com/androidplot/Plot.java +++ b/androidplot-core/src/main/java/com/androidplot/Plot.java @@ -145,25 +145,25 @@ public enum BorderStyle { */ public enum RenderMode { /** - * Use a second thread and an off-screen buffer to do drawing. This is the preferred method - * of drawing dynamic data and static data that consists of a large number of points. This mode - * provides more efficient CPU utilization at the cost of increased memory usage. As of - * version 0.5.1 this is the default RenderMode. + * Do everything in the primary thread. This is the preferred method of drawing static charts + * and dynamic data that consists of a small number of points. This mode uses less memory at + * the cost of poor CPU utilization. * - * XML value: use_background_thread + * XML value: use_main_thread * @since 0.5.1 */ - USE_BACKGROUND_THREAD, + USE_MAIN_THREAD, /** - * Do everything in the primary thread. This is the preferred method of drawing static charts - * and dynamic data that consists of a small number of points. This mode uses less memory at - * the cost of poor CPU utilization. + * Use a second thread and an off-screen buffer to do drawing. This is the preferred method + * of drawing dynamic data and static data that consists of a large number of points. This mode + * provides more efficient CPU utilization at the cost of increased memory usage. As of + * version 0.5.1 this is the default RenderMode. * - * XML value: use_main_thread + * XML value: use_background_thread * @since 0.5.1 */ - USE_MAIN_THREAD + USE_BACKGROUND_THREAD } private BoxModel boxModel = new BoxModel(); diff --git a/androidplot-core/src/main/res/values/attrs.xml b/androidplot-core/src/main/res/values/attrs.xml index 535ccf18..5c714912 100644 --- a/androidplot-core/src/main/res/values/attrs.xml +++ b/androidplot-core/src/main/res/values/attrs.xml @@ -30,8 +30,8 @@ Supported Elements: - + From b365c634ad994386c85440d656e69071870a8beb Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Sun, 19 Sep 2021 17:29:06 -0500 Subject: [PATCH 07/30] fix androidplot artifact coordinates (#112) --- androidplot-core/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/androidplot-core/build.gradle b/androidplot-core/build.gradle index 3d255cc2..e81e40a2 100644 --- a/androidplot-core/build.gradle +++ b/androidplot-core/build.gradle @@ -143,7 +143,7 @@ afterEvaluate { // You can then customize attributes of the publication as shown below. groupId = 'com.androidplot' - artifactId = 'androidplot' + artifactId = 'androidplot-core' version = theVersionName pom { From 0a2e28251a4458a3486db4da0a4e2f856d07a7bb Mon Sep 17 00:00:00 2001 From: Nick Fellows Date: Sun, 19 Sep 2021 18:05:52 -0500 Subject: [PATCH 08/30] #104 - Add support for redrawing plots inside recyclerview + example (#113) --- .../src/main/java/com/androidplot/Plot.java | 86 +++++--- demoapp/build.gradle | 15 +- demoapp/src/main/AndroidManifest.xml | 1 + .../com/androidplot/demos/MainActivity.java | 193 ------------------ .../com/androidplot/demos/MainActivity.kt | 122 +++++++++++ .../androidplot/demos/RecyclerViewActivity.kt | 130 ++++++++++++ demoapp/src/main/res/layout/main.xml | 7 + .../main/res/layout/recyclerview_example.xml | 31 +++ .../res/layout/recyclerview_example_item.xml | 25 +++ docs/quickstart.md | 2 +- docs/release_notes.md | 7 + 11 files changed, 397 insertions(+), 222 deletions(-) delete mode 100644 demoapp/src/main/java/com/androidplot/demos/MainActivity.java create mode 100644 demoapp/src/main/java/com/androidplot/demos/MainActivity.kt create mode 100644 demoapp/src/main/java/com/androidplot/demos/RecyclerViewActivity.kt create mode 100644 demoapp/src/main/res/layout/recyclerview_example.xml create mode 100644 demoapp/src/main/res/layout/recyclerview_example_item.xml diff --git a/androidplot-core/src/main/java/com/androidplot/Plot.java b/androidplot-core/src/main/java/com/androidplot/Plot.java index ac2d6a09..63d2e831 100644 --- a/androidplot-core/src/main/java/com/androidplot/Plot.java +++ b/androidplot-core/src/main/java/com/androidplot/Plot.java @@ -212,6 +212,8 @@ public enum RenderMode { * that is being used. */ private static class BufferedCanvas { + private int lastHeight = 0; + private int lastWidth = 0; private volatile Bitmap bgBuffer; // all drawing is done on this buffer. private volatile Bitmap fgBuffer; private Canvas canvas = new Canvas(); @@ -226,11 +228,21 @@ public synchronized void swap() { fgBuffer = tmp; } + /** + * Used when rendering in background mode and a view is attached + * but not resized, so that rendering buffers will be reinitialized. + */ + public void resizeToLast() { + resize(lastHeight, lastWidth); + } + public synchronized void resize(int h, int w) { if (w <= 0 || h <= 0) { bgBuffer = null; fgBuffer = null; } else { + lastHeight = h; + lastWidth = w; try { bgBuffer = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); fgBuffer = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); @@ -408,35 +420,43 @@ protected final void init(Context context, AttributeSet attrs, int defStyle) { layoutManager.onPostInit(); if (renderMode == RenderMode.USE_BACKGROUND_THREAD) { - renderThread = new Thread(new Runnable() { - @Override - public void run() { - - keepRunning = true; - while (keepRunning) { - isIdle = false; - synchronized (pingPong) { - Canvas c = pingPong.getCanvas(); - renderOnCanvas(c); - pingPong.swap(); - } - synchronized (renderSync) { - postInvalidate(); - // prevent this thread from becoming an orphan - // after the view is destroyed - if (keepRunning) { - try { - renderSync.wait(); - } catch (InterruptedException e) { - keepRunning = false; - } - } + startBackgroundRendering(); + } + } + + protected void startBackgroundRendering() { + if(renderThread != null) { + return; + } + + renderThread = new Thread(() -> { + System.out.println("Thread started with id " + this.hashCode()); + + keepRunning = true; + while (keepRunning) { + isIdle = false; + synchronized (pingPong) { + Canvas c = pingPong.getCanvas(); + renderOnCanvas(c); + pingPong.swap(); + } + synchronized (renderSync) { + postInvalidate(); + // prevent this thread from becoming an orphan + // after the view is destroyed + if (keepRunning) { + try { + renderSync.wait(); + } catch (InterruptedException e) { + keepRunning = false; } } - pingPong.recycle(); } - }, "Androidplot renderThread"); - } + } + System.out.println("Thread exited with id " + this.hashCode()); + renderThread = null; + pingPong.recycle(); + }, "Androidplot renderThread"); } /** @@ -745,11 +765,12 @@ public void redraw() { // only enter synchronized block if the call is expected to block OR // if the render thread is idle, so we know that we won't have to wait to // obtain a lock. - if (isIdle) { + if (renderThread != null && isIdle) { synchronized (renderSync) { renderSync.notify(); } } + } else if(renderMode == RenderMode.USE_MAIN_THREAD) { // are we on the UI thread? @@ -778,6 +799,17 @@ protected void onDetachedFromWindow() { } } + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + // necessary to support rendering in recyclerview etc. + if(renderMode == RenderMode.USE_BACKGROUND_THREAD && renderThread == null) { + pingPong.resizeToLast(); + startBackgroundRendering(); + renderThread.start(); + } + } @Override protected synchronized void onSizeChanged (int w, int h, int oldw, int oldh) { diff --git a/demoapp/build.gradle b/demoapp/build.gradle index a5583eb9..b8b76251 100644 --- a/demoapp/build.gradle +++ b/demoapp/build.gradle @@ -15,12 +15,13 @@ */ buildscript { - + ext.kotlin_version = '1.5.31' repositories { mavenCentral() } dependencies { classpath 'com.github.triplet.gradle:play-publisher:3.6.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -29,9 +30,14 @@ plugins { id 'com.github.triplet.play' version '3.6.0' } +apply plugin: 'kotlin-android' + dependencies { + implementation "androidx.core:core-ktx:1.6.0" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation project(':androidplot-core') implementation 'com.crittercism:crittercism-android-agent:5.4.0' + implementation 'androidx.recyclerview:recyclerview:1.2.1' debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3' @@ -43,6 +49,10 @@ dependencies { android { compileSdkVersion theCompileSdkVersion + buildFeatures { + viewBinding true + } + defaultConfig { versionCode theVersionCode versionName theVersionName @@ -82,3 +92,6 @@ play { serviceAccountCredentials.set(file(System.getenv("PUBLISHER_ACCT_JSON_FILE") ?: "publisher.json")) track = 'beta' } +repositories { + mavenCentral() +} diff --git a/demoapp/src/main/AndroidManifest.xml b/demoapp/src/main/AndroidManifest.xml index 89f1428f..16245b9f 100644 --- a/demoapp/src/main/AndroidManifest.xml +++ b/demoapp/src/main/AndroidManifest.xml @@ -95,6 +95,7 @@ + diff --git a/demoapp/src/main/java/com/androidplot/demos/MainActivity.java b/demoapp/src/main/java/com/androidplot/demos/MainActivity.java deleted file mode 100644 index 86dadb68..00000000 --- a/demoapp/src/main/java/com/androidplot/demos/MainActivity.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright 2015 AndroidPlot.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.androidplot.demos; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; -import android.view.View; -import android.widget.Button; -import com.crittercism.app.Crittercism; - -public class MainActivity extends Activity { - - private static final String TAG = MainActivity.class.getName(); - - // DO *NOT* CHANGE THIS LINE! (CI-MATCH-POPULATE) - private static final String CRITTERCISM_APP_ID = null; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (CRITTERCISM_APP_ID != null) { - Log.d(TAG, "Crittercism initialized."); - Crittercism.initialize(getApplicationContext(), CRITTERCISM_APP_ID); - } - - setContentView(R.layout.main); - - Button startAnimatedXYPlotExButton = (Button) findViewById(R.id.animatedXYPlotExButton); - startAnimatedXYPlotExButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, AnimatedXYPlotActivity.class)); - } - }); - - 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 - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, SimplePieChartActivity.class)); - } - }); - - Button startDynamicXYExButton = (Button) findViewById(R.id.startDynamicXYExButton); - startDynamicXYExButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, DynamicXYPlotActivity.class)); - } - }); - - 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 - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, SimpleXYPlotActivity.class)); - } - }); - - Button startBarPlotExButton = (Button) findViewById(R.id.startBarPlotExButton); - startBarPlotExButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, BarPlotExampleActivity.class)); - } - }); - - Button startOrSensorExButton = (Button) findViewById(R.id.startOrSensorExButton); - startOrSensorExButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, OrientationSensorExampleActivity.class)); - } - }); - - Button startDualScaleExButton = (Button) findViewById(R.id.startDualScaleExButton); - startDualScaleExButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, DualScaleActivity.class)); - } - }); - - Button startTimeSeriesExButon = (Button) findViewById(R.id.startTimeSeriesExButton); - startTimeSeriesExButon.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, TimeSeriesActivity.class)); - } - }); - - Button startStepChartExButton = (Button) findViewById(R.id.startStepChartExButton); - startStepChartExButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, StepChartExampleActivity.class)); - } - }); - - Button startScrollZoomExButton = (Button) findViewById(R.id.startScrollZoomButton); - startScrollZoomExButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, TouchZoomExampleActivity.class)); - } - }); - - Button startXyRegionExampleButton = (Button) findViewById(R.id.startXyRegionExampleButton); - startXyRegionExampleButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, XYRegionExampleActivity.class)); - } - }); - - - Button listViewExButton = (Button) findViewById(R.id.startXyListViewExButton); - listViewExButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, ListViewActivity.class)); - } - }); - - Button startXYPlotWithBgImgExampleButton = (Button) findViewById(R.id.startXYPlotWithBgImgExample); - startXYPlotWithBgImgExampleButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, XYPlotWithBgImgActivity.class)); - } - }); - - // ECG - Button startECGExampleButton = (Button) findViewById(R.id.startECGExample); - startECGExampleButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, ECGExample.class)); - } - }); - - // f(x) plot - Button fxPlotExampleButton = (Button) findViewById(R.id.fxPlotExample); - fxPlotExampleButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, FXPlotExampleActivity.class)); - } - }); - - // bubble chart - Button bubbleChartButton = (Button) findViewById(R.id.bubbleChartExample); - bubbleChartButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, BubbleChartActivity.class)); - } - }); - } -} diff --git a/demoapp/src/main/java/com/androidplot/demos/MainActivity.kt b/demoapp/src/main/java/com/androidplot/demos/MainActivity.kt new file mode 100644 index 00000000..6c8320e1 --- /dev/null +++ b/demoapp/src/main/java/com/androidplot/demos/MainActivity.kt @@ -0,0 +1,122 @@ +/* + * Copyright 2021 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.crittercism.app.Crittercism +import android.content.Intent +import android.util.Log +import com.androidplot.demos.databinding.MainBinding + +class MainActivity : Activity() { + + private lateinit var binding: MainBinding + + companion object { + private val TAG = MainActivity::class.java.name + + // DO *NOT* CHANGE THIS LINE! (CI-MATCH-POPULATE) + private val CRITTERCISM_APP_ID: String? = null + } + + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = MainBinding.inflate(layoutInflater) + if (CRITTERCISM_APP_ID != null) { + Log.d(TAG, "Crittercism initialized.") + Crittercism.initialize(applicationContext, CRITTERCISM_APP_ID) + } + + binding.animatedXYPlotExButton.setOnClickListener { + startActivity(Intent(this, AnimatedXYPlotActivity::class.java)) + } + + binding.startScatterExButton.setOnClickListener { + startActivity(Intent(this, ScatterPlotActivity::class.java)) + } + + binding.startSimplePieExButton.setOnClickListener { + startActivity(Intent(this, SimplePieChartActivity::class.java)) + } + + binding.startDynamicXYExButton.setOnClickListener { + startActivity(Intent(this@MainActivity, DynamicXYPlotActivity::class.java)) + } + + binding.startCandlestickExButton.setOnClickListener { + startActivity(Intent(this@MainActivity, CandlestickChartActivity::class.java)) + } + + binding.startSimpleXYExButton.setOnClickListener { + startActivity(Intent(this@MainActivity, SimpleXYPlotActivity::class.java)) + } + + binding.startBarPlotExButton.setOnClickListener { + startActivity(Intent(this@MainActivity, BarPlotExampleActivity::class.java)) + } + + binding.startOrSensorExButton.setOnClickListener { + startActivity(Intent(this@MainActivity, OrientationSensorExampleActivity::class.java)) + } + + binding.startDualScaleExButton.setOnClickListener { + startActivity(Intent(this@MainActivity, DualScaleActivity::class.java)) + } + + binding.startTimeSeriesExButton.setOnClickListener { + startActivity(Intent(this@MainActivity, TimeSeriesActivity::class.java)) + } + + binding.startStepChartExButton.setOnClickListener { + startActivity(Intent(this@MainActivity, StepChartExampleActivity::class.java)) + } + + binding.startScrollZoomButton.setOnClickListener { + startActivity(Intent(this@MainActivity, TouchZoomExampleActivity::class.java)) + } + + binding.startXyRegionExampleButton.setOnClickListener { + startActivity(Intent(this@MainActivity, XYRegionExampleActivity::class.java)) + } + + binding.startXyListViewExButton.setOnClickListener { + startActivity(Intent(this@MainActivity, ListViewActivity::class.java)) + } + + binding.startXyRecyclerViewExButton.setOnClickListener { + startActivity(Intent(this@MainActivity, RecyclerViewActivity::class.java)) + } + + binding.startXYPlotWithBgImgExample.setOnClickListener { + startActivity(Intent(this@MainActivity, XYPlotWithBgImgActivity::class.java)) + } + + binding.startECGExample.setOnClickListener { + startActivity(Intent(this@MainActivity, ECGExample::class.java)) + } + + binding.fxPlotExample.setOnClickListener { + startActivity(Intent(this@MainActivity, FXPlotExampleActivity::class.java)) + } + + binding.bubbleChartExample.setOnClickListener { + startActivity(Intent(this@MainActivity, BubbleChartActivity::class.java)) + } + + setContentView(binding.root) + } +} \ No newline at end of file diff --git a/demoapp/src/main/java/com/androidplot/demos/RecyclerViewActivity.kt b/demoapp/src/main/java/com/androidplot/demos/RecyclerViewActivity.kt new file mode 100644 index 00000000..194ee6e0 --- /dev/null +++ b/demoapp/src/main/java/com/androidplot/demos/RecyclerViewActivity.kt @@ -0,0 +1,130 @@ +/* + * Copyright 2021 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.Color +import com.androidplot.ui.SeriesBundle +import android.os.Bundle +import android.view.ViewGroup +import android.view.LayoutInflater +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.androidplot.demos.databinding.RecyclerviewExampleBinding +import com.androidplot.demos.databinding.RecyclerviewExampleItemBinding +import com.androidplot.util.PixelUtils +import com.androidplot.xy.* +import java.util.* + +class RecyclerViewActivity : Activity() { + private lateinit var binding: RecyclerviewExampleBinding + + companion object { + private const val NUM_PLOTS = 10 + private const val NUM_POINTS_PER_SERIES = 10 + private const val NUM_SERIES_PER_PLOT = 5 + } + + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + PixelUtils.init(this) + + binding = RecyclerviewExampleBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.recyclerView.setHasFixedSize(true) + binding.recyclerView.layoutManager = LinearLayoutManager(this) + binding.recyclerView.adapter = MyRecyclerViewAdapter() + } + + class MyRecyclerViewHolder( + private val binding: RecyclerviewExampleItemBinding + ) : RecyclerView.ViewHolder(binding.root) { + fun bind(data: List>, title: String) { + val plot = binding.plot + plot.clear() + plot.title.text = title + data.map { plot.addSeries(it.series, it.formatter) } + plot.redraw() + } + } + + class MyRecyclerViewAdapter : RecyclerView.Adapter() { + private val seriesData = generateData() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyRecyclerViewHolder { + val itemBinding = RecyclerviewExampleItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return MyRecyclerViewHolder(itemBinding) + } + + override fun onBindViewHolder(holder: MyRecyclerViewHolder, position: Int) { + holder.bind(seriesData[position], "Series $position") + } + + override fun getItemCount() = seriesData.size + + private fun generateData(): List>> { + val theData = mutableListOf>>() + fun generateBundle(seriesLabel: String): SeriesBundle { + val generator = Random() + val nums = ArrayList() + for (j in 0 until NUM_POINTS_PER_SERIES) { + nums.add(generator.nextFloat()) + } + + val formatter = LineAndPointFormatter( + Color.rgb( + java.lang.Double.valueOf(Math.random() * 255).toInt(), + java.lang.Double.valueOf(Math.random() * 255).toInt(), + java.lang.Double.valueOf(Math.random() * 255).toInt() + ), + Color.rgb( + java.lang.Double.valueOf(Math.random() * 255).toInt(), + java.lang.Double.valueOf(Math.random() * 255).toInt(), + java.lang.Double.valueOf(Math.random() * 255).toInt() + ), + null, null + ) + + // for fun, configure interpolation on the formatter: + formatter.interpolationParams = CatmullRomInterpolator.Params( + 20, + CatmullRomInterpolator.Type.Centripetal + ) + + return SeriesBundle( + SimpleXYSeries( + nums, + SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, + seriesLabel + ), + formatter + ) + } + + for (i in 0 until NUM_PLOTS) { + val seriesList: MutableList> = + ArrayList(NUM_SERIES_PER_PLOT) + + for (k in 0 until NUM_SERIES_PER_PLOT) { + seriesList.add(generateBundle("S$k")) + } + theData.add(seriesList) + } + return theData + } + } +} \ No newline at end of file diff --git a/demoapp/src/main/res/layout/main.xml b/demoapp/src/main/res/layout/main.xml index 590087de..3a2d873f 100644 --- a/demoapp/src/main/res/layout/main.xml +++ b/demoapp/src/main/res/layout/main.xml @@ -118,6 +118,13 @@ android:text="ListView of XYPlots" android:enabled="true"/> +