diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 00000000..1e022fdb
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,14 @@
+Thanks for taking the time to help make Androidplot better! Please note
+that issues should be submitted on Github for bugs only.
+
+How-to questions may be posted under the [Androidplot tag on Stack Overflow](https://stackoverflow.com/questions/tagged/androidplot)
+and feature requests etc. may be posted to the [Google Group Forum](https://groups.google.com/forum/#!forum/androidplot).
+
+When possible, please include the following in your bug report:
+
+* Description of the problem
+* Steps to reproduce the issue
+* Version(s) of Androidplot being used
+* A stacktrace if the issue is causing a crash
+
+
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..d38f337e
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,57 @@
+name: build androidplot
+on: [push]
+jobs:
+ build-and-test:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Code
+ uses: actions/checkout@v3
+
+ - name: Set Up JDK 17
+ uses: actions/setup-java@v2
+ with:
+ distribution: 'adopt'
+ java-version: '17'
+
+ - name: Build and Test
+ run: ./gradlew testDebugUnitTest jacocoTestDebugUnitTestReport
+
+ - name: Build Release
+ env:
+ ENCODED: ${{ secrets.DEMOAPP_KEYSTORE }}
+ KEYSTORE: keystore.jks
+ KEY_ALIAS: Key0
+ KEYSTORE_PASSWORD: ${{ secrets.DEMOAPP_KEYSTORE_PASSWORD }}
+ KEY_PASSWORD: ${{ secrets.DEMOAPP_KEY_PASSWORD }}
+ run: |
+ echo $ENCODED | base64 -di > "${GITHUB_WORKSPACE}/demoapp/keystore.jks"
+ ./gradlew assembleRelease
+
+# TODO: get javadoc working again at some point
+# - name: Javadoc
+# run: ./gradlew javadoc
+
+ - name: Code Coverage
+ run: bash <(curl -s https://codecov.io/bash)
+
+ - uses: actions/upload-artifact@v3
+ with:
+ name: Core Library
+ path: androidplot-core/build/outputs/aar/*.aar
+ retention-days: 30
+
+ - uses: actions/upload-artifact@v3
+ if: github.ref_name == 'master'
+ with:
+ name: Demoapp APK
+ path: demoapp/build/outputs/apk/release/*.apk
+ retention-days: 30
+
+ - name: Stage for Maven Central
+ if: github.ref_name == 'master'
+ env:
+ OSSRH_ACTOR: ${{ secrets.OSSRH_ACTOR }}
+ OSSRH_TOKEN: ${{ secrets.OSSRH_TOKEN }}
+ SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
+ SIGNING_PASSWORD: ${{ secrets.GPG_SIGNING_PASSWORD }}
+ run: ./gradlew publish
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 58204be4..694f2995 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,5 @@ DemoApp/.settings
DemoApp/bin
DemoApp/gen
DemoApp/target
-.idea/libraries
-.idea/*.xml
+.idea
**/R.java
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 00000000..f49a4e16
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
\ No newline at end of file
diff --git a/README.md b/README.md
index de4e8522..9617a26e 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,11 @@
-#  Androidplot [](https://circleci.com/gh/halfhp/androidplot) [](https://codecov.io/gh/halfhp/androidplot) [](https://twitter.com/androidplot)
+#  Androidplot [](https://www.appbrain.com/stats/libraries/details/androidplot/androidplot) [](http://codix.io/gh/repo/halfhp/androidplot) [](https://codecov.io/gh/halfhp/androidplot) [](https://twitter.com/androidplot)
-A library for creating dynamic and static charts in Android apps. It’s designed from the ground up for Android,
-compatible with all versions of Android from 1.6 onward and is **used by over
-[1,000 apps](http://www.appbrain.com/stats/libraries/details/androidplot/androidplot) on Google Play**.
+A library for creating dynamic and static charts in Android apps.
-[](http://codix.io/gh/repo/halfhp/androidplot)
+Androidplot is compatible with all versions of Android from 1.6 onward, works equally with Kotlin and Java codebases
+and is compatible with Jetpack compose.
-If you enjoy the lib, please [rate us on codix.io](http://codix.io/repo/halfhp/androidplot)!
+If you enjoy the lib, please [rate us on codix.io](http://codix.io/gh/repo/halfhp/androidplot)!
@@ -43,7 +42,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 2016 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 e063b01f..b37b2b4a 100644
--- a/androidplot-core/build.gradle
+++ b/androidplot-core/build.gradle
@@ -14,31 +14,84 @@
* limitations under the License.
*/
-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'
+plugins {
+ id 'com.android.library'
+ id 'com.mxalbert.gradle.jacoco-android'
+ id 'maven-publish'
+ id 'signing'
+}
+
+class AttrMarkdown extends DefaultTask {
+
+ def inFile
+ def outFile
+
+ @TaskAction
+ def generate() {
+ def input = project.file(inFile)
+
+ def output = project.file(outFile)
+ if (output.exists()) {
+ output.delete()
+ }
+ output.parentFile.mkdirs()
+
+ input.text.findAll(//) { match, g1 ->
+ g1
+ if (!g1.startsWith("NODOC")) {
+ output.append(g1)
+ output.append "\n\n"
+ }
+ }
+ }
+}
+
+/**
+ * Generates xml attrs markdown docs. To run:
+ * at the command line from the project root dir type:
+ * ./gradlew generateAttrsMarkdown
+ *
+ * The generated doc will appear in / replace androidplot/docs/attrs.md
+ */
+task generateAttrsMarkdown(type: AttrMarkdown) {
+ inFile = { "src/main/res/values/attrs.xml" }
+ outFile = { "../docs/attrs.md" }
+}
android {
- compileSdkVersion theCompileSdkVersion
- buildToolsVersion theBuildToolsVersion
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
+ }
+
defaultConfig {
- versionCode theVersionCode
- versionName theVersionName
+ compileSdk theCompileSdkVersion
minSdkVersion theMinSdkVersion
targetSdkVersion theTargetSdkVersion
testApplicationId "com.androidplot.test"
}
- /**
- * TODO: enable and address lint issues.
- */
- lintOptions {
+ testOptions {
+ unitTests.all {
+ jacoco {
+ includeNoLocationClasses = true
+ jacoco.excludes = ['jdk.internal.*']
+ }
+ }
+ }
+ lint {
abortOnError false
}
- buildToolsVersion '25.0.0'
+ namespace 'com.androidplot'
+
+ publishing {
+ singleVariant("release") {
+ withSourcesJar()
+ withJavadocJar()
+ }
+ }
}
group = 'com.androidplot'
@@ -49,99 +102,88 @@ def gitUrl = 'https://github.com/halfhp/androidplot.git'
dependencies {
- compile 'com.halfhp.fig:figlib:1.0.3'
- compile 'com.android.support:support-annotations:24.2.0'
- testCompile "org.mockito:mockito-core:1.10.19"
- testCompile group: 'junit', name: 'junit', version: '4.12'
- testCompile "org.robolectric:robolectric:3.1"
-
- // temp fix for:
- // https://github.com/robolectric/robolectric/issues/1932
- testCompile 'org.khronos:opengl-api:gl1.1-android-2.1_r1'
-}
-
-task javadoc(type: Javadoc) {
- source = android.sourceSets.main.java.srcDirs
- classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
- failOnError false
- options {
- links "http://docs.oracle.com/javase/7/docs/api/"
- linksOffline "http://d.android.com/reference","${android.sdkDirectory}/docs/reference"
- }
- exclude '**/BuildConfig.java'
- exclude '**/R.java'
-}
+ implementation 'com.halfhp.fig:figlib:1.0.11'
+ implementation 'androidx.annotation:annotation:1.8.2'
-task javadocJar(type: Jar, dependsOn: javadoc) {
- classifier = 'javadoc'
- from javadoc.destinationDir
+ testImplementation "org.mockito:mockito-core:5.7.0"
+ testImplementation group: 'junit', name: 'junit', version: '4.13.2'
+ testImplementation "org.robolectric:robolectric:4.7.3"
}
-task sourcesJar(type: Jar) {
- classifier = 'sources'
- from android.sourceSets.main.java.srcDirs
-}
+// TODO: get javadoc working again at some point
+//task javadoc(type: Javadoc) {
+// source = android.sourceSets.main.java.srcDirs
+// classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
+// failOnError false
+// options {
+// links "http://docs.oracle.com/javase/7/docs/api/"
+// linksOffline "http://d.android.com/reference","${android.sdkDirectory}/docs/reference"
+// }
+// exclude '**/BuildConfig.java'
+// exclude '**/R.java'
+//}
+//
+
+
+// TODO: get javadoc working again at some point
+//javadoc {
+// options.overview = "src/main/java/overview.html"
+//}
+
+afterEvaluate {
+ publishing {
+ repositories {
+ maven {
+ name = "MavenCentral"
+ url = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
+ credentials {
+ username = System.getenv("OSSRH_ACTOR")
+ password = System.getenv("OSSRH_TOKEN")
+ }
+ }
+ }
-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'
+ publications {
+ release(MavenPublication) {
+ from components.release
+
+ // You can then customize attributes of the publication as shown below.
+ groupId = 'com.androidplot'
+ artifactId = 'androidplot-core'
+ 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'
+ developers {
+ developer {
+ id = 'halfhp'
+ name = 'Nick Fellows'
+ email = 'halfhp@gmail.com'
+ }
+ }
+ scm {
+ connection = gitUrl
+ developerConnection = gitUrl
+ url = gitUrl
}
- }
- scm {
- connection gitUrl
- developerConnection gitUrl
- url siteUrl
-
}
}
}
}
}
-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')
-
-
- 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"
-}
-
-artifacts {
- archives javadocJar
- archives sourcesJar
+signing {
+ def signingKey = System.getenv("SIGNING_KEY")
+ def signingPassword = System.getenv("SIGNING_PASSWORD")
+ useInMemoryPgpKeys(signingKey, signingPassword)
+ sign(publishing.publications)
}
diff --git a/androidplot-core/src/main/AndroidManifest.xml b/androidplot-core/src/main/AndroidManifest.xml
index e83183d1..3043c930 100644
--- a/androidplot-core/src/main/AndroidManifest.xml
+++ b/androidplot-core/src/main/AndroidManifest.xml
@@ -16,5 +16,5 @@
-
+
\ No newline at end of file
diff --git a/androidplot-core/src/main/java/com/androidplot/Plot.java b/androidplot-core/src/main/java/com/androidplot/Plot.java
index f2eab84a..63d2e831 100644
--- a/androidplot-core/src/main/java/com/androidplot/Plot.java
+++ b/androidplot-core/src/main/java/com/androidplot/Plot.java
@@ -1,1011 +1,1077 @@
-/*
- * 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.*;
-import android.os.Build;
-import android.os.Looper;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import com.androidplot.exception.PlotRenderException;
-import com.androidplot.ui.*;
-import com.androidplot.ui.Formatter;
-import com.androidplot.ui.TextOrientation;
-import com.androidplot.ui.widget.TextLabelWidget;
-import com.androidplot.ui.SeriesRenderer;
-import com.androidplot.util.AttrUtils;
-import com.androidplot.util.DisplayDimensions;
-import com.androidplot.util.PixelUtils;
-import com.androidplot.ui.HorizontalPositioning;
-import com.androidplot.ui.VerticalPositioning;
-import com.halfhp.fig.*;
-
-import java.lang.reflect.Field;
-import java.util.*;
-
-/**
- * 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();
- }
- });
- }
- }
-
- /**
- * 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));
- }
- }
- Fig.configure(getContext(), this, attrHash);
- }
- }
-
- 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 extends RendererType> 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 extends RendererType> 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 extends RendererType> 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(Canvas canvas) {
- 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.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;
+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 {
+ /**
+ * 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,
+
+ /**
+ * 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
+ }
+ 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 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();
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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);
+ } 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) {
+ 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;
+ }
+ }
+ }
+ }
+ System.out.println("Thread exited with id " + this.hashCode());
+ renderThread = null;
+ 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 extends RendererType> 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 extends RendererType> 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 extends RendererType> 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 (renderThread != null && 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 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) {
+
+ // 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/Region.java b/androidplot-core/src/main/java/com/androidplot/Region.java
index 2ace04a7..f22f9115 100644
--- a/androidplot-core/src/main/java/com/androidplot/Region.java
+++ b/androidplot-core/src/main/java/com/androidplot/Region.java
@@ -17,7 +17,7 @@
package com.androidplot;
-import com.androidplot.util.*;
+import com.androidplot.util.FastNumber;
/**
* A one dimensional region represented by a starting and ending value.
@@ -71,7 +71,7 @@ public Number length() {
Number l = getMax() == null || getMin() == null ?
null : getMax().doubleValue() - getMin().doubleValue();
if(l != null) {
- cachedLength = new FastNumber(l);
+ cachedLength = FastNumber.orNull(l);
}
}
return cachedLength;
@@ -215,8 +215,8 @@ public void setMin(Number min) {
} else {
this.min = null;
}
- } else {
- this.min = new FastNumber(min);
+ } else if (this.min == null || !this.min.equals(min)) {
+ this.min = FastNumber.orNull(min);
}
}
@@ -237,8 +237,8 @@ public void setMax(Number max) {
} else {
this.max = null;
}
- } else {
- this.max = new FastNumber(max);
+ } else if (this.max == null || !this.max.equals(max)) {
+ this.max = FastNumber.orNull(max);
}
}
@@ -249,4 +249,20 @@ public void setMax(Number max) {
public boolean isDefined() {
return min != null && max != null;
}
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("Region{");
+ sb.append("min=").append(min);
+ sb.append(", max=").append(max);
+ sb.append(", cachedLength=").append(cachedLength);
+ sb.append(", defaults=");
+ if (defaults != this) {
+ sb.append(defaults);
+ } else {
+ sb.append("this");
+ }
+ sb.append('}');
+ return sb.toString();
+ }
}
diff --git a/androidplot-core/src/main/java/com/androidplot/SeriesRegistry.java b/androidplot-core/src/main/java/com/androidplot/SeriesRegistry.java
index 78f3d805..7a7b3023 100644
--- a/androidplot-core/src/main/java/com/androidplot/SeriesRegistry.java
+++ b/androidplot-core/src/main/java/com/androidplot/SeriesRegistry.java
@@ -19,7 +19,7 @@
import com.androidplot.ui.Formatter;
import com.androidplot.ui.SeriesBundle;
-import java.io.*;
+import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -38,7 +38,7 @@ public List getSeriesAndFormatterList() {
return registry;
}
public List getSeriesList() {
- List result = new ArrayList<>();
+ List result = new ArrayList<>(registry.size());
for(SeriesBundle sfPair : registry) {
result.add(sfPair.getSeries());
}
diff --git a/androidplot-core/src/main/java/com/androidplot/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/PieChart.java b/androidplot-core/src/main/java/com/androidplot/pie/PieChart.java
index 615efaaa..35e5e416 100644
--- a/androidplot-core/src/main/java/com/androidplot/pie/PieChart.java
+++ b/androidplot-core/src/main/java/com/androidplot/pie/PieChart.java
@@ -37,15 +37,18 @@ public class PieChart extends Plot {
+
+ private PieChart pieChart;
+
+ public PieLegendWidget(LayoutManager layoutManager, PieChart pieChart,
+ Size widgetSize,
+ TableModel tableModel,
+ Size iconSize) {
+ super(tableModel, layoutManager, widgetSize, iconSize);
+ this.pieChart = pieChart;
+ }
+
+ @Override
+ protected void drawIcon(@NonNull Canvas canvas, @NonNull RectF iconRect, @NonNull PieLegendItem item) {
+ canvas.drawRect(iconRect, item.formatter.getFillPaint());
+ }
+
+ @Override
+ protected List getLegendItems() {
+ final List legendItems = new ArrayList<>();
+ for(SeriesBundle item : pieChart.getRegistry().getLegendEnabledItems()) {
+ legendItems.add(new PieLegendItem(item.getSeries(), item.getFormatter()));
+ }
+ return legendItems;
+ }
+}
diff --git a/androidplot-core/src/main/java/com/androidplot/pie/PieRenderer.java b/androidplot-core/src/main/java/com/androidplot/pie/PieRenderer.java
index 8b663a82..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;
@@ -30,11 +29,14 @@
*/
public class PieRenderer extends SeriesRenderer {
+ private static final float FULL_PIE_DEGS = 360f;
+ private static final float HALF_PIE_DEGS = 180f;
+
// starting angle to use when drawing the first radial line of the first segment.
private float startDegs = 0;
// number of degrees to extend from startDegs; can be used to "shape" the pie chart.
- private float extentDegs = 360;
+ private float extentDegs = FULL_PIE_DEGS;
// TODO: express donut in units other than px.
private float donutSize = 0.5f;
@@ -55,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:
@@ -167,7 +169,7 @@ protected void drawSegment(Canvas canvas, RectF bounds, Segment seg, SegmentForm
canvas.drawLine(r1Inner.x, r1Inner.y, r1Outer.x, r1Outer.y, f.getRadialEdgePaint());
canvas.drawLine(r2Inner.x, r2Inner.y, r2Outer.x, r2Outer.y, f.getRadialEdgePaint());
} else {
- canvas.save(Canvas.CLIP_SAVE_FLAG);
+ canvas.save();
Path chart = new Path();
chart.addCircle(cx, cy, outerRad, Path.Direction.CW);
Path inside = new Path();
@@ -240,7 +242,7 @@ protected PointF calculateLineEnd(float x, float y, float rad, float deg) {
protected PointF calculateLineEnd(PointF origin, float rad, float deg) {
- double radians = deg * Math.PI / 180F;
+ double radians = deg * Math.PI / HALF_PIE_DEGS;
double x = rad * Math.cos(radians);
double y = rad * Math.sin(radians);
@@ -292,11 +294,10 @@ public Segment getContainingSegment(PointF point) {
float dx = point.x - origin.x;
float dy = point.y - origin.y;
double theta = Math.atan2(dy, dx);
- double angle = (theta * (180f / Math.PI));
+ double angle = (theta * (HALF_PIE_DEGS / Math.PI));
if (angle < 0) {
- // convert angle to 0-360 range with 0 being in the
- // traditional "east" orientation:
- angle += 360f;
+ // bring into 0-360 range
+ angle += FULL_PIE_DEGS;
}
// find the segment whose starting and ending angle (degs) contains
@@ -310,10 +311,16 @@ public Segment getContainingSegment(PointF point) {
float lastOffset = offset;
float sweep = (float) (scale * (values[i]) * extentDegs);
offset += sweep;
- offset = offset % 360;
+ offset = offset % FULL_PIE_DEGS;
final double dist = signedDistance(offset, angle);
- if(dist > 0 && dist <= signedDistance(offset, lastOffset)) {
+ double endDist = signedDistance(offset, lastOffset);
+ if(endDist < 0) {
+ // segment accounts for more than 50% of the pie and wrapped around
+ // need to correct:
+ endDist = FULL_PIE_DEGS + endDist;
+ }
+ if(dist > 0 && dist <= endDist) {
return sfPair.getSeries();
}
i++;
@@ -328,10 +335,10 @@ public Segment getContainingSegment(PointF point) {
* @return
*/
protected static float degsToScreenDegs(float degs) {
- degs = degs % 360;
+ degs = degs % FULL_PIE_DEGS;
if (degs > 0) {
- return 360 - degs;
+ return FULL_PIE_DEGS - degs;
} else {
return degs;
}
@@ -344,12 +351,12 @@ protected static float degsToScreenDegs(float degs) {
* @return
*/
protected static double signedDistance(double angle1, double angle2) {
- double d = Math.abs(angle1 - angle2) % 360;
- double r = d > 180 ? 360 - d : d;
+ double d = Math.abs(angle1 - angle2) % FULL_PIE_DEGS;
+ double r = d > HALF_PIE_DEGS ? FULL_PIE_DEGS - d : d;
//calculate sign
- int sign = (angle1 - angle2 >= 0 && angle1 - angle2 <= 180)
- || (angle1 - angle2 <= -180 && angle1 - angle2 >= -360) ? 1 : -1;
+ int sign = (angle1 - angle2 >= 0 && angle1 - angle2 <= HALF_PIE_DEGS)
+ || (angle1 - angle2 <= -HALF_PIE_DEGS && angle1 - angle2 >= -FULL_PIE_DEGS) ? 1 : -1;
r *= sign;
return r;
}
@@ -359,7 +366,7 @@ protected static double signedDistance(double angle1, double angle2) {
* @param degs
*/
protected static void validateInputDegs(float degs) {
- if(degs < 0 || degs > 360) {
+ if(degs < 0 || degs > FULL_PIE_DEGS) {
throw new IllegalArgumentException("Degrees values must be between 0.0 and 360.");
}
}
diff --git a/androidplot-core/src/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/DynamicTableModel.java b/androidplot-core/src/main/java/com/androidplot/ui/DynamicTableModel.java
index c7c7f066..ff973b9d 100644
--- a/androidplot-core/src/main/java/com/androidplot/ui/DynamicTableModel.java
+++ b/androidplot-core/src/main/java/com/androidplot/ui/DynamicTableModel.java
@@ -149,10 +149,10 @@ public TableModelIterator(DynamicTableModel dynamicTableModel, RectF tableRect,
calculatedRows = dynamicTableModel.getNumRows();
// round up:
- calculatedColumns = new Float((totalElements / (float) calculatedRows) + 0.5).intValue();
+ calculatedColumns = Float.valueOf((totalElements / (float) calculatedRows) + 0.5f).intValue();
} else if(dynamicTableModel.getNumRows() == 0 && dynamicTableModel.getNumColumns() >= 1) {
calculatedColumns = dynamicTableModel.getNumColumns();
- calculatedRows = new Float((totalElements / (float) calculatedColumns) + 0.5).intValue();
+ calculatedRows = Float.valueOf((totalElements / (float) calculatedColumns) + 0.5f).intValue();
// unlimited rows and columns (impossible) so default a single row with n columns:
}else if(dynamicTableModel.getNumColumns() == 0 && dynamicTableModel.getNumRows() == 0) {
calculatedRows = 1;
diff --git a/androidplot-core/src/main/java/com/androidplot/ui/Formatter.java b/androidplot-core/src/main/java/com/androidplot/ui/Formatter.java
index 8f2a354b..d920ab36 100644
--- a/androidplot-core/src/main/java/com/androidplot/ui/Formatter.java
+++ b/androidplot-core/src/main/java/com/androidplot/ui/Formatter.java
@@ -48,7 +48,11 @@ public Formatter(Context ctx, int xmlCfgId) {
}
public void configure(Context ctx, int xmlCfgId) {
- Fig.configure(ctx, this, xmlCfgId);
+ try {
+ Fig.configure(ctx, this, xmlCfgId);
+ } catch (FigException e) {
+ throw new RuntimeException(e);
+ }
}
/**
diff --git a/androidplot-core/src/main/java/com/androidplot/ui/LayoutManager.java b/androidplot-core/src/main/java/com/androidplot/ui/LayoutManager.java
index d9107442..45f79a43 100644
--- a/androidplot-core/src/main/java/com/androidplot/ui/LayoutManager.java
+++ b/androidplot-core/src/main/java/com/androidplot/ui/LayoutManager.java
@@ -16,10 +16,15 @@
package com.androidplot.ui;
-import android.graphics.*;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.graphics.Region;
import android.view.MotionEvent;
import android.view.View;
-import com.androidplot.exception.PlotRenderException;
+
import com.androidplot.ui.widget.Widget;
import com.androidplot.util.DisplayDimensions;
import com.androidplot.util.LinkedLayerList;
@@ -77,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);
}
@@ -86,11 +91,11 @@ public void draw(Canvas canvas) throws PlotRenderException {
}
for (Widget widget : elements()) {
try {
- canvas.save(Canvas.ALL_SAVE_FLAG);
+ canvas.save();
PositionMetrics metrics = widget.getPositionMetrics();
float elementWidth = widget.getWidthPix(displayDims.paddedRect.width());
float elementHeight = widget.getHeightPix(displayDims.paddedRect.height());
- PointF coords = widget.getElementCoordinates(elementHeight,
+ PointF coords = Widget.calculateCoordinates(elementHeight,
elementWidth, displayDims.paddedRect, metrics);
DisplayDimensions dims = widget.getWidgetDimensions();
@@ -132,9 +137,9 @@ public void draw(Canvas canvas) throws PlotRenderException {
}
}
- private void drawSpacing(Canvas canvas, RectF outer, RectF inner, Paint paint) {
+ private static void drawSpacing(Canvas canvas, RectF outer, RectF inner, Paint paint) {
try {
- canvas.save(Canvas.ALL_SAVE_FLAG);
+ canvas.save();
canvas.clipRect(inner, Region.Op.DIFFERENCE);
canvas.drawRect(outer, paint);
} finally {
diff --git a/androidplot-core/src/main/java/com/androidplot/ui/PositionMetrics.java b/androidplot-core/src/main/java/com/androidplot/ui/PositionMetrics.java
index 3381f20f..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,6 +16,8 @@
package com.androidplot.ui;
+import androidx.annotation.NonNull;
+
public class PositionMetrics implements Comparable {
private HorizontalPosition horizontalPosition;
@@ -47,7 +49,7 @@ public void setAnchor(Anchor anchor) {
}
@Override
- public int compareTo(PositionMetrics o) {
+ public int compareTo(@NonNull PositionMetrics o) {
if(this.layerDepth < o.layerDepth) {
return -1;
} else if(this.layerDepth == o.layerDepth) {
diff --git a/androidplot-core/src/main/java/com/androidplot/ui/RenderStack.java b/androidplot-core/src/main/java/com/androidplot/ui/RenderStack.java
index d9b9d812..99227563 100644
--- a/androidplot-core/src/main/java/com/androidplot/ui/RenderStack.java
+++ b/androidplot-core/src/main/java/com/androidplot/ui/RenderStack.java
@@ -23,7 +23,7 @@
import java.util.List;
/**
- * A stack of series to be rendered. The stack order is immutable but individual elements may be
+ * A stack of series to be rendered. The stack order is immutable but individual elements may be
* manipulated via the public methods of {@link RenderStack.StackElement}.
*/
public class RenderStack {
diff --git a/androidplot-core/src/main/java/com/androidplot/ui/SeriesRenderer.java b/androidplot-core/src/main/java/com/androidplot/ui/SeriesRenderer.java
index 882bdcfc..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
+ */
+public abstract class LegendWidget extends Widget {
+
+ private static final float DEFAULT_TEXT_SIZE_DP = 20;
+
+ private TableModel tableModel;
+ private Size iconSize;
+
+ private Paint textPaint;
+ private Paint iconBackgroundPaint;
+ private Paint iconBorderPaint;
+
+ private boolean drawIconBackgroundEnabled = true;
+ private boolean drawIconBorderEnabled = true;
+
+ private Comparator legendItemComparator;
+
+ {
+ textPaint = new Paint();
+ textPaint.setColor(Color.LTGRAY);
+ textPaint.setTextSize(PixelUtils.spToPix(DEFAULT_TEXT_SIZE_DP));
+ textPaint.setAntiAlias(true);
+
+ iconBackgroundPaint = new Paint();
+ iconBackgroundPaint.setColor(Color.BLACK);
+
+ iconBorderPaint = new Paint();
+ iconBorderPaint.setColor(Color.TRANSPARENT);
+ iconBorderPaint.setStyle(Paint.Style.STROKE);
+ }
+
+
+ public LegendWidget(@NonNull TableModel tableModel, @NonNull LayoutManager layoutManager,
+ @NonNull Size size, @NonNull Size iconSize) {
+ super(layoutManager, size);
+ setTableModel(tableModel);
+ this.iconSize = iconSize;
+ }
+
+ @Override
+ protected void doOnDraw(Canvas canvas, RectF widgetRect) {
+ final List items = getLegendItems();
+ if(legendItemComparator != null) {
+ Collections.sort(items, legendItemComparator);
+ }
+ final Iterator cellRectIterator = tableModel.getIterator(widgetRect, items.size());
+ for(ItemT item : items) {
+ final RectF cellRect = cellRectIterator.next();
+ final RectF iconRect = getIconRect(cellRect);
+ beginDrawingCell(canvas, iconRect);
+ drawItem(canvas, iconRect, item);
+ finishDrawingCell(canvas, cellRect, iconRect, item);
+ }
+ }
+
+ protected void drawItem(@NonNull Canvas canvas, @NonNull RectF iconRect, @NonNull ItemT item) {
+ drawIcon(canvas, iconRect, item);
+ }
+
+ /**
+ * Draw the icon representing the legend item
+ * @param canvas
+ * @param iconRect The space to be occupied by the icon.
+ * @param item
+ */
+ protected abstract void drawIcon(@NonNull Canvas canvas, @NonNull RectF iconRect, @NonNull ItemT item);
+
+ /**
+ *
+ * @return The list of legend items to be drawn. This is used to calculate table dimensions etc.
+ */
+ protected abstract List getLegendItems();
+
+ private RectF getIconRect(RectF cellRect) {
+ float cellRectCenterY = cellRect.top + (cellRect.height()/2);
+ RectF iconRect = iconSize.getRectF(cellRect);
+
+ // center the icon rect vertically
+ float centeredIconOriginY = cellRectCenterY - (iconRect.height()/2);
+ iconRect.offsetTo(cellRect.left + 1, centeredIconOriginY);
+ return iconRect;
+ }
+
+ /**
+ * Done at the start of rendering a new cell. Whatever is drawn here will be beneath the rest
+ * of the cell content; typically used to draw backgrounds.
+ * @param canvas
+ * @param iconRect
+ */
+ protected void beginDrawingCell(Canvas canvas, RectF iconRect) {
+
+ if(drawIconBackgroundEnabled && iconBackgroundPaint != null) {
+ canvas.drawRect(iconRect, iconBackgroundPaint);
+ }
+ }
+
+ /**
+ * Done at the end of rendering a new cell. Whatever is drawn here will be on top of
+ * the rest of the cell content; typically used to draw borders and text.
+ * @param canvas
+ * @param cellRect
+ * @param iconRect
+ * @param legendItem
+ */
+ protected void finishDrawingCell(Canvas canvas, RectF cellRect, RectF iconRect, LegendItem legendItem) {
+
+ if(drawIconBorderEnabled && iconBorderPaint != null) {
+ canvas.drawRect(iconRect, iconBorderPaint);
+ }
+
+ float centeredTextOriginY = getRectCenterY(cellRect) + (FontUtils.getFontHeight(textPaint)/2);
+
+ if (textPaint.getTextAlign().equals(Paint.Align.RIGHT)) {
+ canvas.drawText(legendItem.getTitle(), iconRect.left - 2, centeredTextOriginY, textPaint);
+ } else {
+ canvas.drawText(legendItem.getTitle(), iconRect.right + 2, centeredTextOriginY, textPaint);
+ }
+ }
+
+ protected static float getRectCenterY(RectF cellRect) {
+ return cellRect.top + (cellRect.height()/2);
+ }
+
+ public synchronized void setTableModel(TableModel tableModel) {
+ this.tableModel = tableModel;
+ }
+
+ public Paint getTextPaint() {
+ return textPaint;
+ }
+
+ public void setTextPaint(Paint textPaint) {
+ this.textPaint = textPaint;
+ }
+
+ public boolean isDrawIconBackgroundEnabled() {
+ return drawIconBackgroundEnabled;
+ }
+
+ public void setDrawIconBackgroundEnabled(boolean drawIconBackgroundEnabled) {
+ this.drawIconBackgroundEnabled = drawIconBackgroundEnabled;
+ }
+
+ public boolean isDrawIconBorderEnabled() {
+ return drawIconBorderEnabled;
+ }
+
+ public void setDrawIconBorderEnabled(boolean drawIconBorderEnabled) {
+ this.drawIconBorderEnabled = drawIconBorderEnabled;
+ }
+
+ public Size getIconSize() {
+ return iconSize;
+ }
+
+ public void setIconSize(Size iconSize) {
+ this.iconSize = iconSize;
+ }
+
+ public Comparator getLegendItemComparator() {
+ return legendItemComparator;
+ }
+
+ /**
+ * Set a scheme for sorting the display order or legend items. By default no sorting is applied
+ * and {@link com.androidplot.Series} items typically appear in the order which the series was
+ * added to the {@link com.androidplot.Plot}.
+ * @param legendItemComparator
+ */
+ public void setLegendItemComparator(Comparator legendItemComparator) {
+ this.legendItemComparator = legendItemComparator;
+ }
+}
diff --git a/androidplot-core/src/main/java/com/androidplot/ui/widget/TextLabelWidget.java b/androidplot-core/src/main/java/com/androidplot/ui/widget/TextLabelWidget.java
index 8c6e84cd..06bf54ff 100644
--- a/androidplot-core/src/main/java/com/androidplot/ui/widget/TextLabelWidget.java
+++ b/androidplot-core/src/main/java/com/androidplot/ui/widget/TextLabelWidget.java
@@ -101,7 +101,7 @@ public void doOnDraw(Canvas canvas, RectF widgetRect) {
Anchor.CENTER);
try {
- canvas.save(Canvas.ALL_SAVE_FLAG);
+ canvas.save();
canvas.translate(start.x, start.y);
switch (orientation) {
case HORIZONTAL:
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 6009ce36..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,7 +17,9 @@
package com.androidplot.ui.widget;
import android.graphics.*;
-import com.androidplot.exception.PlotRenderException;
+import androidx.annotation.Nullable;
+import androidx.annotation.NonNull;
+
import com.androidplot.ui.*;
import com.androidplot.util.DisplayDimensions;
import com.androidplot.ui.HorizontalPositioning;
@@ -42,6 +44,7 @@ public abstract class Widget implements BoxModelable, Resizable {
private LayoutManager layoutManager;
private Rotation rotation = Rotation.NONE;
+ private RectF lastWidgetRect = null;
public enum Rotation {
NINETY_DEGREES,
@@ -77,6 +80,7 @@ public void setAnchor(Anchor anchor) {
/**
* Same as {@link #position(float, HorizontalPositioning, float, VerticalPositioning, Anchor)}
* but with the anchor parameter defaulted to the upper left corner.
+ *
* @param x
* @param horizontalPositioning
* @param y
@@ -87,11 +91,11 @@ public void position(float x, HorizontalPositioning horizontalPositioning, float
}
/**
- * @param x X-Coordinate of the top left corner of element. When using RELATIVE, must be a value between 0 and 1.
+ * @param x X-Coordinate of the top left corner of element. When using RELATIVE, must be a value between 0 and 1.
* @param horizontalPositioning LayoutType to use when orienting this element's X-Coordinate.
- * @param y Y_VALS_ONLY-Coordinate of the top-left corner of element. When using RELATIVE, must be a value between 0 and 1.
- * @param verticalPositioning LayoutType to use when orienting this element's Y_VALS_ONLY-Coordinate.
- * @param anchor The point of reference used by this positioning call.
+ * @param y Y_VALS_ONLY-Coordinate of the top-left corner of element. When using RELATIVE, must be a value between 0 and 1.
+ * @param verticalPositioning LayoutType to use when orienting this element's Y_VALS_ONLY-Coordinate.
+ * @param anchor The point of reference used by this positioning call.
*/
public void position(float x, HorizontalPositioning horizontalPositioning, float y,
VerticalPositioning verticalPositioning, Anchor anchor) {
@@ -271,7 +275,7 @@ public float getMarginRight() {
* into this Widget's size or position is altered.
*/
public synchronized void refreshLayout() {
- if(positionMetrics == null) {
+ if (positionMetrics == null) {
// make sure positionMetrics have been set. this method can be
// automatically called during xml configuration of certain params
// before the widget is fully configured.
@@ -279,7 +283,7 @@ public synchronized void refreshLayout() {
}
float elementWidth = getWidthPix(plotDimensions.paddedRect.width());
float elementHeight = getHeightPix(plotDimensions.paddedRect.height());
- PointF coords = getElementCoordinates(elementHeight,
+ PointF coords = calculateCoordinates(elementHeight,
elementWidth, plotDimensions.paddedRect, positionMetrics);
RectF widgetRect = new RectF(coords.x, coords.y,
@@ -296,69 +300,90 @@ public synchronized void layout(final DisplayDimensions plotDimensions) {
refreshLayout();
}
- public PointF getElementCoordinates(float height, float width, RectF viewRect, PositionMetrics metrics) {
- float x = metrics.getXPositionMetric().getPixelValue(viewRect.width()) + viewRect.left;
- float y = metrics.getYPositionMetric().getPixelValue(viewRect.height()) + viewRect.top;
- PointF point = new PointF(x, y);
- return PixelUtils.sub(point, getAnchorOffset(width, height, metrics.getAnchor()));
- }
+
+ public static PointF calculateCoordinates(float height, float width, RectF viewRect, PositionMetrics metrics) {
+ float x = metrics.getXPositionMetric().getPixelValue(viewRect.width()) + viewRect.left;
+ float y = metrics.getYPositionMetric().getPixelValue(viewRect.height()) + viewRect.top;
+ PointF point = new PointF(x, y);
+ return PixelUtils.sub(point, getAnchorOffset(width, height, metrics.getAnchor()));
+ }
public static PointF getAnchorOffset(float width, float height, Anchor anchor) {
- PointF point = new PointF();
- switch (anchor) {
- case LEFT_TOP:
- break;
- case LEFT_MIDDLE:
- point.set(0, height / 2);
- break;
- case LEFT_BOTTOM:
- point.set(0, height);
- break;
- case RIGHT_TOP:
- point.set(width, 0);
- break;
- case RIGHT_BOTTOM:
- point.set(width, height);
- break;
- case RIGHT_MIDDLE:
- point.set(width, height / 2);
- break;
- case TOP_MIDDLE:
- point.set(width / 2, 0);
- break;
- case BOTTOM_MIDDLE:
- point.set(width / 2, height);
- break;
- case CENTER:
- point.set(width / 2, height / 2);
- break;
- default:
- throw new IllegalArgumentException("Unsupported anchor location: " + anchor);
- }
- return point;
+ PointF point = new PointF();
+ switch (anchor) {
+ case LEFT_TOP:
+ break;
+ case LEFT_MIDDLE:
+ point.set(0, height / 2);
+ break;
+ case LEFT_BOTTOM:
+ point.set(0, height);
+ break;
+ case RIGHT_TOP:
+ point.set(width, 0);
+ break;
+ case RIGHT_BOTTOM:
+ point.set(width, height);
+ break;
+ case RIGHT_MIDDLE:
+ point.set(width, height / 2);
+ break;
+ case TOP_MIDDLE:
+ point.set(width / 2, 0);
+ break;
+ case BOTTOM_MIDDLE:
+ point.set(width / 2, height);
+ break;
+ case CENTER:
+ point.set(width / 2, height / 2);
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported anchor location: " + anchor);
}
+ return point;
+ }
public static PointF getAnchorCoordinates(RectF widgetRect, Anchor anchor) {
- return PixelUtils.add(new PointF(widgetRect.left, widgetRect.top),
- getAnchorOffset(widgetRect.width(), widgetRect.height(), anchor));
- }
+ return PixelUtils.add(new PointF(widgetRect.left, widgetRect.top),
+ getAnchorOffset(widgetRect.width(), widgetRect.height(), anchor));
+ }
+
+ public static PointF getAnchorCoordinates(float x, float y, float width, float height, Anchor anchor) {
+ return getAnchorCoordinates(new RectF(x, y, x + width, y + height), anchor);
+ }
- public static PointF getAnchorCoordinates(float x, float y, float width, float height, Anchor anchor) {
- return getAnchorCoordinates(new RectF(x, y, x+width, y+height), anchor);
+ private void checkSize(@NonNull RectF widgetRect) {
+ if (lastWidgetRect == null || !lastWidgetRect.equals(widgetRect)) {
+ onResize(lastWidgetRect, widgetRect);
}
+ lastWidgetRect = widgetRect;
+ }
+
+ /**
+ * Called whenever the height or width of the Widget's reserved space has changed,
+ * immediately before {@link #doOnDraw(Canvas, RectF)}.
+ * May be used to efficiently carry out expensive operations only when necessary.
+ *
+ * @param oldRect
+ * @param newRect
+ */
+ 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);
}
canvas.save();
- final RectF paddedRect = applyRotation(canvas, widgetDimensions.paddedRect);
- doOnDraw(canvas, paddedRect);
+ final RectF widgetRect = applyRotation(canvas, widgetDimensions.paddedRect);
+ checkSize(widgetRect);
+ doOnDraw(canvas, widgetRect);
canvas.restore();
if (borderPaint != null) {
- drawBorder(canvas, paddedRect);
+ drawBorder(canvas, widgetRect);
}
}
}
@@ -395,7 +420,7 @@ protected RectF applyRotation(Canvas canvas, RectF rect) {
throw new UnsupportedOperationException("Not yet implemented.");
}
- if(rotation != Rotation.NONE) {
+ if (rotation != Rotation.NONE) {
canvas.rotate(rotationDegs, cx, cy);
}
return rect;
@@ -413,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/util/AttrUtils.java b/androidplot-core/src/main/java/com/androidplot/util/AttrUtils.java
index b34e7d47..853ae25a 100644
--- a/androidplot-core/src/main/java/com/androidplot/util/AttrUtils.java
+++ b/androidplot-core/src/main/java/com/androidplot/util/AttrUtils.java
@@ -237,7 +237,7 @@ private static Number getIntFloatDimenValue(TypedArray attrs, int valueAttr, Num
} else if (valueType == TypedValue.TYPE_FLOAT) {
result = attrs.getFloat(valueAttr, defaultValue.floatValue());
} else {
- throw new IllegalArgumentException("Invalid value type - must be float or dimension.");
+ throw new IllegalArgumentException("Invalid value type - must be int, float or dimension.");
}
}
return result;
diff --git a/androidplot-core/src/main/java/com/androidplot/util/FastNumber.java b/androidplot-core/src/main/java/com/androidplot/util/FastNumber.java
index 049431b3..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,11 +1,14 @@
package com.androidplot.util;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
/**
* An extension of {@link Number} optimized for speed at the cost of memory.
*/
public class FastNumber extends Number {
- private Number number;
+ @NonNull private final Number number;
private boolean hasDoublePrimitive;
private boolean hasFloatPrimitive;
private boolean hasIntPrimitive;
@@ -14,7 +17,25 @@ public class FastNumber extends Number {
private float floatPrimitive;
private int intPrimitive;
- public FastNumber(Number number) {
+ /**
+ * Safe-instantiator of FastNumber; returns a null result if the input Number is also null.
+ * @param number
+ * @return
+ */
+ public static FastNumber orNull(@NonNull Number number) {
+ if(number == null) {
+ return null;
+ } else {
+ return new FastNumber(number);
+ }
+ }
+
+ private FastNumber(@NonNull Number number) {
+
+ //noinspection ConstantConditions //in case someone ignores the @NonNull annotation
+ if (number == null) {
+ throw new IllegalArgumentException("number parameter cannot be null");
+ }
// avoid nested instances of FastNumber :
if(number instanceof FastNumber) {
@@ -63,4 +84,36 @@ public double doubleValue() {
}
return doublePrimitive;
}
+
+ /**
+ * To be equal, two instances must both be instances of {@link FastNumber}. The inner {@link
+ * #number} field must also be a common type. Numbers which are mathematically equal are not
+ * necessarily equal. This keeps with the java implementation of common Number classes where for
+ * instance {@code new Integer(0).equals(new Double(0))} returns {@code false}
+ */
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ FastNumber that = (FastNumber) o;
+
+ return number.equals(that.number);
+
+ }
+
+ @Override
+ public int hashCode() {
+ return number.hashCode();
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return String.valueOf(doubleValue());
+ }
}
diff --git a/androidplot-core/src/main/java/com/androidplot/util/Redrawer.java b/androidplot-core/src/main/java/com/androidplot/util/Redrawer.java
index 00174027..4bc118e0 100644
--- a/androidplot-core/src/main/java/com/androidplot/util/Redrawer.java
+++ b/androidplot-core/src/main/java/com/androidplot/util/Redrawer.java
@@ -17,11 +17,12 @@
package com.androidplot.util;
import android.util.Log;
+
import com.androidplot.Plot;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
/**
@@ -52,12 +53,12 @@ public class Redrawer implements Runnable {
* @param startImmediately If true, invokes run() immediately after construction.
*/
public Redrawer(List plots, float maxRefreshRate, boolean startImmediately) {
- this.plots = new ArrayList<>();
+ this.plots = new ArrayList<>(plots.size());
for(Plot plot : plots) {
this.plots.add(new WeakReference<>(plot));
}
setMaxRefreshRate(maxRefreshRate);
- thread = new Thread(this);
+ thread = new Thread(this, "Androidplot Redrawer");
thread.start();
if(startImmediately) {
start();
@@ -65,7 +66,7 @@ public Redrawer(List plots, float maxRefreshRate, boolean startImmediately
}
public Redrawer(Plot plot, float maxRefreshRate, boolean startImmediately) {
- this(Arrays.asList(new Plot[]{plot}), maxRefreshRate, startImmediately);
+ this(Collections.singletonList(plot), maxRefreshRate, startImmediately);
}
/**
@@ -121,7 +122,7 @@ public void run() {
}
}
}
- } catch(InterruptedException e) {
+ } catch (InterruptedException ignored) {
} finally {
Log.d(TAG, "Redrawer thread exited.");
diff --git a/androidplot-core/src/main/java/com/androidplot/util/SeriesUtils.java b/androidplot-core/src/main/java/com/androidplot/util/SeriesUtils.java
index a9f09192..b9526a24 100644
--- a/androidplot-core/src/main/java/com/androidplot/util/SeriesUtils.java
+++ b/androidplot-core/src/main/java/com/androidplot/util/SeriesUtils.java
@@ -16,10 +16,14 @@
package com.androidplot.util;
-import com.androidplot.*;
-import com.androidplot.xy.*;
+import com.androidplot.Region;
+import com.androidplot.xy.FastXYSeries;
+import com.androidplot.xy.OrderedXYSeries;
+import com.androidplot.xy.RectRegion;
+import com.androidplot.xy.XYConstraints;
+import com.androidplot.xy.XYSeries;
-import java.util.*;
+import java.util.List;
/**
* Utilities for dealing with Series data.
@@ -61,7 +65,6 @@ public static Region minMaxY(XYSeries... seriesList) {
* @since 0.9.7
*/
public static RectRegion minMax(XYConstraints constraints, List seriesList) {
- // TODO: this is inefficient...clean it up!
return minMax(constraints, seriesList.toArray(new XYSeries[seriesList.size()]));
}
@@ -82,31 +85,24 @@ public static RectRegion minMax(XYConstraints constraints, XYSeries... seriesArr
for (XYSeries series : seriesArray) {
// if this is an advanced xy series then minMax have already been calculated for us:
- if(series instanceof FastXYSeries) {
+ if (series instanceof FastXYSeries) {
final RectRegion b = ((FastXYSeries) series).minMax();
if(b == null) {
+ //this series doesn't currently have min or max region (might be empty)
continue;
}
- if(constraints == null) {
+ if(constraints == null || constraints.contains(b)) {
bounds.union(b);
- } else {
- if(constraints.contains(b.getMinX(), b.getMinY())) {
- bounds.union(b.getMinX(), b.getMinY());
- }
- if(constraints.contains(b.getMaxX(), b.getMaxY())) {
- bounds.union(b.getMaxX(), b.getMaxY());
- }
+ continue;
}
+ }
+ for (int i = 0; i < series.size(); i++) {
+ final Number xi = series.getX(i);
+ final Number yi = series.getY(i);
- } else if (series.size() > 0) {
- for (int i = 0; i < series.size(); i++) {
- final Number xi = series.getX(i);
- final Number yi = series.getY(i);
-
- // if constraints have been set, make sure this xy coordinate exists within them:
- if (constraints == null || constraints.contains(xi, yi)) {
- bounds.union(xi, yi);
- }
+ // if constraints have been set, make sure this xy coordinate exists within them:
+ if (constraints == null || constraints.contains(xi, yi)) {
+ bounds.union(xi, yi);
}
}
}
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 c696bbf2..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 bar, RectF rect) {
// null yVals are skipped:
if(bar.getY() == null) {
@@ -366,6 +373,7 @@ public int compare(Bar bar1, Bar bar2) {
} else {
return Float.valueOf(bar1.yPix).compareTo(bar2.yPix);
}
+ case IN_ORDER:
case SIDE_BY_SIDE:
case STACKED:
default:
diff --git a/androidplot-core/src/main/java/com/androidplot/xy/BubbleFormatter.java b/androidplot-core/src/main/java/com/androidplot/xy/BubbleFormatter.java
index b351358b..3a794b5c 100644
--- a/androidplot-core/src/main/java/com/androidplot/xy/BubbleFormatter.java
+++ b/androidplot-core/src/main/java/com/androidplot/xy/BubbleFormatter.java
@@ -16,11 +16,12 @@
package com.androidplot.xy;
-import android.content.*;
-import android.graphics.*;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Paint;
-import com.androidplot.ui.*;
-import com.androidplot.util.*;
+import com.androidplot.ui.SeriesRenderer;
+import com.androidplot.util.PixelUtils;
/**
* Format for drawing a value using {@link BubbleRenderer}.
@@ -51,7 +52,7 @@ public class BubbleFormatter extends XYSeriesFormatter {
setPointLabeler(new PointLabeler() {
@Override
public String getLabel(BubbleSeries series, int index) {
- return series.getZ(index) + "";
+ return String.valueOf(series.getZ(index));
}
});
}
diff --git a/androidplot-core/src/main/java/com/androidplot/xy/BubbleRenderer.java b/androidplot-core/src/main/java/com/androidplot/xy/BubbleRenderer.java
index 1e0ba9c5..dcebf6cb 100644
--- a/androidplot-core/src/main/java/com/androidplot/xy/BubbleRenderer.java
+++ b/androidplot-core/src/main/java/com/androidplot/xy/BubbleRenderer.java
@@ -3,7 +3,6 @@
import android.graphics.*;
import com.androidplot.Region;
-import com.androidplot.exception.*;
import com.androidplot.ui.*;
import com.androidplot.util.*;
@@ -45,7 +44,7 @@ public BubbleRenderer(XYPlot plot) {
@Override
protected void onRender(Canvas canvas, RectF plotArea, BubbleSeries series,
- FormatterType formatter, RenderStack stack) throws PlotRenderException {
+ FormatterType formatter, RenderStack stack) {
Region magnitudeBounds = calculateBounds();
for(int i = 0; i < series.size(); i++) {
diff --git a/androidplot-core/src/main/java/com/androidplot/xy/CatmullRomInterpolator.java b/androidplot-core/src/main/java/com/androidplot/xy/CatmullRomInterpolator.java
index 31e272f6..aea93ecd 100644
--- a/androidplot-core/src/main/java/com/androidplot/xy/CatmullRomInterpolator.java
+++ b/androidplot-core/src/main/java/com/androidplot/xy/CatmullRomInterpolator.java
@@ -20,7 +20,7 @@
import java.util.List;
/**
- * A primitive implementation of Catmull-Rom interpolation, based on the information found at:
+ * An implementation of Catmull-Rom interpolation, based on the information found at:
* http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/19283471#19283471
*/
public class CatmullRomInterpolator implements Interpolator {
@@ -196,7 +196,7 @@ public List interpolate(XYSeries series, Params params) {
* @return the list of coordinates that define the CatmullRom curve
* between the points defined by index+1 and index+2.
*/
- public List interpolate(XYSeries series, int index, Params params) {
+ protected List interpolate(XYSeries series, int index, Params params) {
List result = new ArrayList<>();
double[] x = new double[4];
double[] y = new double[4];
@@ -251,7 +251,7 @@ public List interpolate(XYSeries series, int index, Params params) {
* position between p1 and p2 to interpolate the value.
* @return
*/
- public static double interpolate(double[] p, double[] time, double t) {
+ protected static double interpolate(double[] p, double[] time, double t) {
double L01 = p[0] * (time[1] - t) / (time[1] - time[0]) + p[1] * (t - time[0]) / (time[1] - time[0]);
double L12 = p[1] * (time[2] - t) / (time[2] - time[1]) + p[2] * (t - time[1]) / (time[2] - time[1]);
double L23 = p[2] * (time[3] - t) / (time[3] - time[2]) + p[3] * (t - time[2]) / (time[3] - time[2]);
diff --git a/androidplot-core/src/main/java/com/androidplot/xy/FastLineAndPointRenderer.java b/androidplot-core/src/main/java/com/androidplot/xy/FastLineAndPointRenderer.java
index e5aa7bc9..df070325 100644
--- a/androidplot-core/src/main/java/com/androidplot/xy/FastLineAndPointRenderer.java
+++ b/androidplot-core/src/main/java/com/androidplot/xy/FastLineAndPointRenderer.java
@@ -16,12 +16,17 @@
package com.androidplot.xy;
-import android.graphics.*;
-import com.androidplot.exception.PlotRenderException;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import androidx.annotation.NonNull;
+
import com.androidplot.ui.RenderStack;
import com.androidplot.ui.SeriesRenderer;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
/**
* A faster implementation of of {@link LineAndPointRenderer}. For performance reasons, has these constraints:
@@ -32,6 +37,11 @@
*/
public class FastLineAndPointRenderer extends XYSeriesRenderer {
+ /**
+ * A line drawn by {@link Canvas#drawLines(float[], int, int, Paint)} must be defined by at
+ * least four points {@code x0, y0, x1, y1}
+ */
+ private static final int MINIMUM_NUMBER_OF_POINTS_TO_DEFINE_A_LINE = 4;
private float[] points;
List segmentOffsets = new ArrayList<>();
List segmentLengths = new ArrayList<>();
@@ -40,7 +50,7 @@ public FastLineAndPointRenderer(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) {
segmentOffsets.clear();
segmentLengths.clear();
@@ -53,11 +63,11 @@ protected void onRender(Canvas canvas, RectF plotArea, XYSeries series, Formatte
int segmentLen = 0;
boolean isLastPointNull = true;
+ PointF resultPoint = new PointF();
for (int i = 0, j = 0; i < series.size(); i++, j+=2) {
Number y = series.getY(i);
Number x = series.getX(i);
- PointF thisPoint;
if (y != null && x != null) {
if(isLastPointNull) {
segmentOffsets.add(j);
@@ -65,9 +75,9 @@ protected void onRender(Canvas canvas, RectF plotArea, XYSeries series, Formatte
isLastPointNull = false;
}
- thisPoint = getPlot().getBounds().transformScreen(x, y, plotArea);
- points[j] = thisPoint.x;
- points[j+1] = thisPoint.y;
+ getPlot().getBounds().transformScreen(resultPoint, x, y, plotArea);
+ points[j] = resultPoint.x;
+ points[j + 1] = resultPoint.y;
segmentLen+=2;
// if this is the last point, account for it in segment lengths:
@@ -90,10 +100,14 @@ protected void onRender(Canvas canvas, RectF plotArea, XYSeries series, Formatte
}
}
- protected void drawSegment(Canvas canvas, float[] points, int offset, int len, Formatter formatter) {
+ protected void drawSegment(@NonNull Canvas canvas,
+ @NonNull float[] points,
+ int offset,
+ int len,
+ Formatter formatter) {
if(formatter.linePaint != null) {
// draw lines:
- if (len >= 4) {
+ if (len >= MINIMUM_NUMBER_OF_POINTS_TO_DEFINE_A_LINE) {
// optimization to avoid using 2x storage space to represent the full path:
if ((len & 2) != 0) {
canvas.drawLines(points, offset, len - 2, formatter.linePaint);
@@ -112,10 +126,16 @@ protected void drawSegment(Canvas canvas, float[] points, int offset, int len, F
}
@Override
- protected void doDrawLegendIcon(Canvas canvas, RectF rect, Formatter formatter) {
+ protected void doDrawLegendIcon(@NonNull Canvas canvas,
+ @NonNull RectF rect,
+ @NonNull Formatter formatter) {
if(formatter.hasLinePaint()) {
canvas.drawLine(rect.left, rect.bottom, rect.right, rect.top, formatter.getLinePaint());
}
+
+ if(formatter.hasVertexPaint()) {
+ canvas.drawPoint(rect.centerX(), rect.centerY(), formatter.getVertexPaint());
+ }
}
/**
diff --git a/androidplot-core/src/main/java/com/androidplot/xy/FastXYSeries.java b/androidplot-core/src/main/java/com/androidplot/xy/FastXYSeries.java
index 4faa4d54..de5d534b 100644
--- a/androidplot-core/src/main/java/com/androidplot/xy/FastXYSeries.java
+++ b/androidplot-core/src/main/java/com/androidplot/xy/FastXYSeries.java
@@ -3,6 +3,10 @@
/**
* An implementation of {@link XYSeries} that defines additional methods to speed up rendering by
* giving a hint to the renderer about the min/max values contained in the series.
+ *
+ * Note that these hints can only be leveraged if the containing XYPlot's constraints completely
+ * contain the FastXYSeries min/max values. If this condition is not met then XYPlot falls back
+ * to manually determining the min/max values of the series that exist within the defined constraints.
*/
public interface FastXYSeries extends XYSeries {
diff --git a/androidplot-core/src/main/java/com/androidplot/xy/FixedSizeEditableXYSeries.java b/androidplot-core/src/main/java/com/androidplot/xy/FixedSizeEditableXYSeries.java
index 240f58dc..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,8 +1,12 @@
package com.androidplot.xy;
-import com.androidplot.util.*;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
-import java.util.*;
+import com.androidplot.util.FastNumber;
+
+import java.util.ArrayList;
+import java.util.List;
/**
* An efficient implementation of {@link EditableXYSeries} intended for use cases where
@@ -14,7 +18,10 @@
*/
public class FixedSizeEditableXYSeries implements EditableXYSeries {
+ @NonNull
private List xVals = new ArrayList<>();
+
+ @NonNull
private List yVals = new ArrayList<>();
private String title;
@@ -24,13 +31,13 @@ public FixedSizeEditableXYSeries(String title, int size) {
}
@Override
- public void setX(Number x, int index) {
- xVals.set(index, new FastNumber(x));
+ public void setX(@Nullable Number x, int index) {
+ xVals.set(index, FastNumber.orNull(x));
}
@Override
- public void setY(Number y, int index) {
- yVals.set(index, new FastNumber(y));
+ public void setY(@Nullable Number y, int index) {
+ yVals.set(index, FastNumber.orNull(y));
}
/**
@@ -44,7 +51,7 @@ public void resize(int size) {
resize(yVals, size);
}
- protected void resize(List list, int size) {
+ protected void resize(@NonNull List list, int size) {
if (size > list.size()) {
while (list.size() < size) {
list.add(null);
diff --git a/androidplot-core/src/main/java/com/androidplot/xy/GroupRenderer.java b/androidplot-core/src/main/java/com/androidplot/xy/GroupRenderer.java
index d2fe4c73..503d624d 100644
--- a/androidplot-core/src/main/java/com/androidplot/xy/GroupRenderer.java
+++ b/androidplot-core/src/main/java/com/androidplot/xy/GroupRenderer.java
@@ -19,7 +19,6 @@
import android.graphics.Canvas;
import android.graphics.RectF;
import android.util.Log;
-import com.androidplot.exception.PlotRenderException;
import com.androidplot.ui.RenderStack;
import com.androidplot.ui.SeriesBundle;
@@ -42,7 +41,7 @@ public GroupRenderer(XYPlot plot) {
@Override
protected void onRender(Canvas canvas, RectF plotArea, XYSeries series,
- FormatterType formatter, RenderStack stack) throws PlotRenderException {
+ FormatterType formatter, RenderStack stack) {
// get all the data associated with this renderer:
diff --git a/androidplot-core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java b/androidplot-core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java
index e8c0c23b..f50d6186 100644
--- a/androidplot-core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java
+++ b/androidplot-core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java
@@ -25,7 +25,6 @@
import com.androidplot.Plot;
import com.androidplot.PlotListener;
import com.androidplot.Region;
-import com.androidplot.exception.PlotRenderException;
import com.androidplot.ui.RenderStack;
import com.androidplot.util.*;
@@ -63,7 +62,7 @@ public void onAfterDraw(Plot source, Canvas canvas) {
}
@Override
- public void onRender(Canvas canvas, RectF plotArea, XYSeries series, FormatterType formatter, RenderStack stack) throws PlotRenderException {
+ public void onRender(Canvas canvas, RectF plotArea, XYSeries series, FormatterType formatter, RenderStack stack) {
drawSeries(canvas, plotArea, series, formatter);
}
@@ -309,7 +308,7 @@ protected void renderPath(Canvas canvas, RectF plotArea, Path path, PointF first
RectF thisRegionRectF = thisRegionTransformed.asRectF();
if (thisRegionRectF != null) {
try {
- canvas.save(Canvas.ALL_SAVE_FLAG);
+ canvas.save();
canvas.clipPath(path);
canvas.drawRect(thisRegionRectF, regionFormatter.getPaint());
} finally {
diff --git a/androidplot-core/src/main/java/com/androidplot/xy/OrderedXYSeries.java b/androidplot-core/src/main/java/com/androidplot/xy/OrderedXYSeries.java
index 659e38ea..820e4330 100644
--- a/androidplot-core/src/main/java/com/androidplot/xy/OrderedXYSeries.java
+++ b/androidplot-core/src/main/java/com/androidplot/xy/OrderedXYSeries.java
@@ -9,13 +9,13 @@ public interface OrderedXYSeries extends XYSeries {
enum XOrder {
/**
* XVals are in strict ascending order such that:
- * x(i) > x(i+1) == true
+ * x(i) < x(i+1) == true
*/
ASCENDING,
/**
* XVals are in strict descending order such that:
- * x(i) < x(i+1) == true
+ * x(i) > x(i+1) == true
*/
DESCENDING,
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 a8793264..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,11 +2,13 @@
import android.graphics.RectF;
import android.graphics.PointF;
+import androidx.annotation.NonNull;
import android.view.*;
import com.androidplot.*;
import com.androidplot.util.*;
+import java.io.Serializable;
import java.util.*;
/**
@@ -35,6 +37,7 @@ public class PanZoom implements View.OnTouchListener {
// rectangle created by the space between two fingers
protected RectF fingersRect;
private View.OnTouchListener delegate;
+ private State state = new State();
// Definition of the touch states
protected enum DragState {
@@ -95,7 +98,42 @@ public enum ZoomLimit {
MIN_TICKS
}
- protected PanZoom(XYPlot plot, Pan pan, Zoom zoom) {
+ // TODO: consider making this immutable / threadsafe
+ public static class State implements Serializable {
+ private Number domainLowerBoundary;
+ private Number domainUpperBoundary;
+ private Number rangeLowerBoundary;
+ private Number rangeUpperBoundary;
+ private BoundaryMode domainBoundaryMode;
+ private BoundaryMode rangeBoundaryMode;
+
+ public void setDomainBoundaries(Number lowerBoundary, Number upperBoundary, BoundaryMode mode) {
+ this.domainLowerBoundary = lowerBoundary;
+ this.domainUpperBoundary = upperBoundary;
+ this.domainBoundaryMode = mode;
+ }
+
+ public void setRangeBoundaries(Number lowerBoundary, Number upperBoundary, BoundaryMode mode) {
+ this.rangeLowerBoundary = lowerBoundary;
+ this.rangeUpperBoundary = upperBoundary;
+ this.rangeBoundaryMode = mode;
+ }
+
+ public void applyDomainBoundaries(@NonNull XYPlot plot) {
+ plot.setDomainBoundaries(domainLowerBoundary, domainUpperBoundary, domainBoundaryMode);
+ }
+
+ public void applyRangeBoundaries(@NonNull XYPlot plot) {
+ plot.setRangeBoundaries(rangeLowerBoundary, rangeUpperBoundary, rangeBoundaryMode);
+ }
+
+ public void apply(@NonNull XYPlot plot) {
+ applyDomainBoundaries(plot);
+ applyRangeBoundaries(plot);
+ }
+ }
+
+ protected PanZoom(@NonNull XYPlot plot, Pan pan, Zoom zoom) {
this.plot = plot;
this.pan = pan;
this.zoom = zoom;
@@ -103,13 +141,32 @@ protected PanZoom(XYPlot plot, Pan pan, Zoom zoom) {
}
// additional constructor not to break api
- protected PanZoom(XYPlot plot, Pan pan, Zoom zoom, ZoomLimit limit) {
+ protected PanZoom(@NonNull XYPlot plot, Pan pan, Zoom zoom, ZoomLimit limit) {
this.plot = plot;
this.pan = pan;
this.zoom = zoom;
this.zoomLimit = limit;
}
+ public State getState() {
+ return this.state;
+ }
+
+ public void setState(@NonNull State state) {
+ this.state = state;
+ state.apply(plot);
+ }
+
+ protected void adjustRangeBoundary(Number lower, Number upper, BoundaryMode mode) {
+ state.setRangeBoundaries(lower, upper, mode);
+ state.applyRangeBoundaries(plot);
+ }
+
+ protected void adjustDomainBoundary(Number lower, Number upper, BoundaryMode mode) {
+ state.setDomainBoundaries(lower, upper, mode);
+ state.applyDomainBoundaries(plot);
+ }
+
/**
* Convenience method for enabling pan/zoom behavior on an instance of {@link XYPlot}, using
* a default behavior of {@link Pan#BOTH} and {@link Zoom#SCALE}.
@@ -117,7 +174,7 @@ protected PanZoom(XYPlot plot, Pan pan, Zoom zoom, ZoomLimit limit) {
* @param plot
* @return
*/
- public static PanZoom attach(XYPlot plot) {
+ public static PanZoom attach(@NonNull XYPlot plot) {
return attach(plot, Pan.BOTH, Zoom.SCALE);
}
@@ -130,7 +187,7 @@ public static PanZoom attach(XYPlot plot) {
* @param zoom
* @return
*/
- public static PanZoom attach(XYPlot plot, Pan pan, Zoom zoom) {
+ public static PanZoom attach(@NonNull XYPlot plot, @NonNull Pan pan, @NonNull Zoom zoom) {
return attach(plot,pan,zoom, ZoomLimit.OUTER);
}
@@ -142,7 +199,7 @@ public static PanZoom attach(XYPlot plot, Pan pan, Zoom zoom) {
* @param limit
* @return
*/
- public static PanZoom attach(XYPlot plot, Pan pan, Zoom zoom, ZoomLimit limit) {
+ public static PanZoom attach(@NonNull XYPlot plot, @NonNull Pan pan, @NonNull Zoom zoom, @NonNull ZoomLimit limit) {
PanZoom pz = new PanZoom(plot, pan, zoom, limit);
plot.setOnTouchListener(pz);
return pz;
@@ -237,12 +294,12 @@ protected void pan(final MotionEvent motionEvent) {
if (EnumSet.of(Pan.HORIZONTAL, Pan.BOTH).contains(pan)) {
Region newBounds = new Region();
calculatePan(oldFirstFinger, newBounds, true);
- plot.setDomainBoundaries(newBounds.getMin(), newBounds.getMax(), BoundaryMode.FIXED);
+ adjustDomainBoundary(newBounds.getMin(), newBounds.getMax(), BoundaryMode.FIXED);
}
if (EnumSet.of(Pan.VERTICAL, Pan.BOTH).contains(pan)) {
Region newBounds = new Region();
calculatePan(oldFirstFinger, newBounds, false);
- plot.setRangeBoundaries(newBounds.getMin(), newBounds.getMax(), BoundaryMode.FIXED);
+ adjustRangeBoundary(newBounds.getMin(), newBounds.getMax(), BoundaryMode.FIXED);
}
plot.redraw();
@@ -291,10 +348,9 @@ protected void calculatePan(final PointF oldFirstFinger, Region bounds, final bo
}
protected boolean isValidScale(float scale) {
- if (Float.isInfinite(scale) || Float.isNaN(scale) || scale > -0.001 && scale < 0.001) {
- return false;
- }
- return true;
+ return !Float.isInfinite(scale)
+ && !Float.isNaN(scale)
+ && (!(scale > -0.001) || !(scale < 0.001));
}
protected void zoom(final MotionEvent motionEvent) {
@@ -349,14 +405,14 @@ protected void zoom(final MotionEvent motionEvent) {
Zoom.STRETCH_BOTH,
Zoom.SCALE).contains(zoom)) {
calculateZoom(newRect, scaleX, true);
- plot.setDomainBoundaries(newRect.left, newRect.right, BoundaryMode.FIXED);
+ adjustDomainBoundary(newRect.left, newRect.right, BoundaryMode.FIXED);
}
if (EnumSet.of(
Zoom.STRETCH_VERTICAL,
Zoom.STRETCH_BOTH,
Zoom.SCALE).contains(zoom)) {
calculateZoom(newRect, scaleY, false);
- plot.setRangeBoundaries(newRect.top, newRect.bottom, BoundaryMode.FIXED);
+ adjustRangeBoundary(newRect.top, newRect.bottom, BoundaryMode.FIXED);
}
plot.redraw();
}
diff --git a/androidplot-core/src/main/java/com/androidplot/xy/RectRegion.java b/androidplot-core/src/main/java/com/androidplot/xy/RectRegion.java
index 7eeb0d73..0bb8988d 100644
--- a/androidplot-core/src/main/java/com/androidplot/xy/RectRegion.java
+++ b/androidplot-core/src/main/java/com/androidplot/xy/RectRegion.java
@@ -20,6 +20,7 @@
import android.graphics.RectF;
import com.androidplot.Region;
+
import java.util.ArrayList;
import java.util.List;
@@ -244,7 +245,7 @@ public Number getHeight() {
* @param y
* @return
*/
- private Number distanceBetween(Number x, Number y) {
+ private static Number distanceBetween(Number x, Number y) {
return Math.abs(x.doubleValue() - y.doubleValue());
}
@@ -344,4 +345,13 @@ public boolean isFullyDefined() {
public boolean contains(Number x, Number y) {
return getxRegion().contains(x) && getyRegion().contains(y);
}
+
+ @Override
+ public String toString() {
+ return "RectRegion{" +
+ "xRegion=" + xRegion +
+ ", yRegion=" + yRegion +
+ ", label='" + label + '\'' +
+ '}';
+ }
}
diff --git a/androidplot-core/src/main/java/com/androidplot/xy/SampledXYSeries.java b/androidplot-core/src/main/java/com/androidplot/xy/SampledXYSeries.java
index b10903b1..f795536b 100644
--- a/androidplot-core/src/main/java/com/androidplot/xy/SampledXYSeries.java
+++ b/androidplot-core/src/main/java/com/androidplot/xy/SampledXYSeries.java
@@ -89,7 +89,7 @@ public void run() {
lastResamplingException = ex;
}
}
- });
+ }, "Androidplot XY Series Sampler");
getZoomLevels().add(thisSeries);
threads.add(thread);
thread.start();
diff --git a/androidplot-core/src/main/java/com/androidplot/xy/SimpleXYSeries.java b/androidplot-core/src/main/java/com/androidplot/xy/SimpleXYSeries.java
index f346694a..b9178123 100644
--- a/androidplot-core/src/main/java/com/androidplot/xy/SimpleXYSeries.java
+++ b/androidplot-core/src/main/java/com/androidplot/xy/SimpleXYSeries.java
@@ -17,10 +17,15 @@
package com.androidplot.xy;
import android.graphics.Canvas;
+
import com.androidplot.Plot;
import com.androidplot.PlotListener;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.NoSuchElementException;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -28,9 +33,6 @@
* A convenience class used to create instances of XYPlot generated from Lists of Numbers.
*/
public class SimpleXYSeries implements EditableXYSeries, OrderedXYSeries, PlotListener {
-
- private static final String TAG = SimpleXYSeries.class.getName();
-
private volatile LinkedList xVals = new LinkedList<>();
private volatile LinkedList yVals = new LinkedList<>();
private volatile String title = null;
@@ -82,10 +84,8 @@ public void onAfterDraw(Plot source, Canvas canvas) {
}
protected static List asNumberList(Number... model) {
- List numbers = new ArrayList<>();
- for(Number n : model) {
- numbers.add(n);
- }
+ List numbers = new ArrayList<>(model.length);
+ Collections.addAll(numbers, model);
return numbers;
}
@@ -141,7 +141,7 @@ public void setModel(List extends Number> model, ArrayFormat format) {
lock.writeLock().lock();
try {
// empty the current values:
- xVals = null;
+ xVals.clear();
yVals.clear();
// make sure the new model has data:
@@ -153,15 +153,16 @@ public void setModel(List extends Number> model, ArrayFormat format) {
// array containing only y-vals. assume x = index:
case Y_VALS_ONLY:
- for(Number n : model) {
- yVals.add(n);
+ yVals.addAll(model);
+ for(int i = 0; i < yVals.size(); i++) {
+ xVals.add(i);
}
break;
// xy interleaved array:
case XY_VALS_INTERLEAVED:
if (xVals == null) {
- xVals = new LinkedList();
+ xVals = new LinkedList<>();
}
if (model.size() % 2 != 0) {
throw new IndexOutOfBoundsException("Cannot auto-generate series from odd-sized xy List.");
@@ -214,7 +215,6 @@ public void resize(int size) {
try {
lock.writeLock().lock();
if (xVals.size() < size) {
-
for (int i = xVals.size(); i < size; i++) {
xVals.add(null);
yVals.add(null);
diff --git a/androidplot-core/src/main/java/com/androidplot/xy/ValueMarker.java b/androidplot-core/src/main/java/com/androidplot/xy/ValueMarker.java
index 30e9aacd..de0436b2 100644
--- a/androidplot-core/src/main/java/com/androidplot/xy/ValueMarker.java
+++ b/androidplot-core/src/main/java/com/androidplot/xy/ValueMarker.java
@@ -16,10 +16,14 @@
package com.androidplot.xy;
+import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
+import android.graphics.RectF;
+
import com.androidplot.ui.PositionMetric;
import com.androidplot.ui.TextOrientation;
+import com.androidplot.util.FontUtils;
/**
* Encapsulates a single axis line marker drawn onto an XYPlot at a specified value.
@@ -27,6 +31,8 @@
*/
public abstract class ValueMarker {
+ 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/XYConstraints.java b/androidplot-core/src/main/java/com/androidplot/xy/XYConstraints.java
index ae22a4ea..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,12 @@
package com.androidplot.xy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
/**
* Calculates the min/max constraints for an xy plane.
+ *
* @since 0.9.7
*/
public class XYConstraints {
@@ -44,113 +48,148 @@ public XYConstraints() {
this(null, null, null, null);
}
- public XYConstraints(Number minX, Number maxX, Number minY, Number maxY) {
+ public XYConstraints(@Nullable Number minX, @Nullable Number maxX, @Nullable Number minY, @Nullable Number maxY) {
this.minX = minX;
this.minY = minY;
this.maxX = maxX;
this.maxY = maxY;
}
+ public boolean contains(@NonNull RectRegion rectRegion) {
+ return contains(rectRegion.getMinY(), rectRegion.getMinY())
+ && contains(rectRegion.getMaxX(), rectRegion.getMaxY());
+ }
+
public boolean contains(Number x, Number y) {
- if(x == null || y == null) {
+ if (x == null || y == null) {
// this is essentially an invisible point:
return false;
- } else {
- final double dx = x.doubleValue();
-
- if(minX != null && dx < minX.doubleValue()) {
- return false;
- } else if(maxX != null && dx > maxX.doubleValue()) {
- return false;
- } else {
- final double dy = y.doubleValue();
- if(minY != null && dy < minY.doubleValue()) {
- return false;
- } else if(maxY != null && dy > maxY.doubleValue()) {
- return false;
- }
- }
+ }
+
+ if (minX == null && maxX == null && minY == null && maxY == null) {
+ //there are no constraints
return true;
}
+
+ final double dx = x.doubleValue();
+ if (minX != null && dx < minX.doubleValue()) {
+ return false;
+ } else if (maxX != null && dx > maxX.doubleValue()) {
+ return false;
+ }
+
+ final double dy = y.doubleValue();
+ if (minY != null && dy < minY.doubleValue()) {
+ return false;
+ } else if (maxY != null && dy > maxY.doubleValue()) {
+ return false;
+ }
+
+ return true;
}
+ @Nullable
public Number getMinX() {
return minX;
}
+ @Nullable
public Number getMaxX() {
return maxX;
}
+ @Nullable
public Number getMinY() {
return minY;
}
+ @Nullable
public Number getMaxY() {
return maxY;
}
+ public void setMinX(@Nullable Number minX) {
+ this.minX = minX;
+ }
+
+ public void setMaxX(@Nullable Number maxX) {
+ this.maxX = maxX;
+ }
+
+ public void setMinY(@Nullable Number minY) {
+ this.minY = minY;
+ }
+
+ public void setMaxY(@Nullable Number maxY) {
+ this.maxY = maxY;
+ }
+
+ @NonNull
public XYFramingModel getDomainFramingModel() {
return domainFramingModel;
}
- public void setDomainFramingModel(XYFramingModel domainFramingModel) {
+ public void setDomainFramingModel(@NonNull XYFramingModel domainFramingModel) {
this.domainFramingModel = domainFramingModel;
}
+ @NonNull
public XYFramingModel getRangeFramingModel() {
return rangeFramingModel;
}
- public void setRangeFramingModel(XYFramingModel rangeFramingModel) {
+ public void setRangeFramingModel(@NonNull XYFramingModel rangeFramingModel) {
this.rangeFramingModel = rangeFramingModel;
}
+ @NonNull
public BoundaryMode getDomainUpperBoundaryMode() {
return domainUpperBoundaryMode;
}
- public void setDomainUpperBoundaryMode(BoundaryMode domainUpperBoundaryMode) {
+ public void setDomainUpperBoundaryMode(@NonNull BoundaryMode domainUpperBoundaryMode) {
this.domainUpperBoundaryMode = domainUpperBoundaryMode;
}
+ @NonNull
public BoundaryMode getDomainLowerBoundaryMode() {
return domainLowerBoundaryMode;
}
- public void setDomainLowerBoundaryMode(BoundaryMode domainLowerBoundaryMode) {
+ public void setDomainLowerBoundaryMode(@NonNull BoundaryMode domainLowerBoundaryMode) {
this.domainLowerBoundaryMode = domainLowerBoundaryMode;
}
+ @NonNull
public BoundaryMode getRangeUpperBoundaryMode() {
return rangeUpperBoundaryMode;
}
- public void setRangeUpperBoundaryMode(BoundaryMode rangeUpperBoundaryMode) {
+ public void setRangeUpperBoundaryMode(@NonNull BoundaryMode rangeUpperBoundaryMode) {
this.rangeUpperBoundaryMode = rangeUpperBoundaryMode;
}
+ @NonNull
public BoundaryMode getRangeLowerBoundaryMode() {
return rangeLowerBoundaryMode;
}
- public void setRangeLowerBoundaryMode(BoundaryMode rangeLowerBoundaryMode) {
+ public void setRangeLowerBoundaryMode(@NonNull BoundaryMode rangeLowerBoundaryMode) {
this.rangeLowerBoundaryMode = rangeLowerBoundaryMode;
}
- public void setMinX(Number minX) {
- this.minX = minX;
- }
-
- public void setMaxX(Number maxX) {
- this.maxX = maxX;
- }
-
- public void setMinY(Number minY) {
- this.minY = minY;
- }
-
- public void setMaxY(Number maxY) {
- this.maxY = maxY;
+ @Override
+ public String toString() {
+ return "XYConstraints{" + "domainFramingModel=" + domainFramingModel +
+ ", rangeFramingModel=" + rangeFramingModel +
+ ", domainUpperBoundaryMode=" + domainUpperBoundaryMode +
+ ", domainLowerBoundaryMode=" + domainLowerBoundaryMode +
+ ", rangeUpperBoundaryMode=" + rangeUpperBoundaryMode +
+ ", rangeLowerBoundaryMode=" + rangeLowerBoundaryMode +
+ ", minX=" + minX +
+ ", maxX=" + maxX +
+ ", minY=" + minY +
+ ", maxY=" + maxY +
+ '}';
}
}
diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java b/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java
index 21c512f6..8fa0571f 100644
--- a/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java
+++ b/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java
@@ -16,23 +16,38 @@
package com.androidplot.xy;
-import android.content.res.*;
-import android.graphics.*;
-
-import com.androidplot.*;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.androidplot.R;
import com.androidplot.Region;
-import com.androidplot.exception.PlotRenderException;
-import com.androidplot.ui.*;
+import com.androidplot.ui.Insets;
+import com.androidplot.ui.LayoutManager;
+import com.androidplot.ui.RenderStack;
+import com.androidplot.ui.Size;
import com.androidplot.ui.widget.Widget;
-import com.androidplot.util.*;
+import com.androidplot.util.AttrUtils;
+import com.androidplot.util.FontUtils;
+import com.androidplot.util.PixelUtils;
+import com.androidplot.util.RectFUtils;
import java.text.DecimalFormat;
import java.text.Format;
-import java.util.*;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.Map;
/**
- * Displays graphical data (lines, points, etc.) annotated with domain and range tick markers.
- * The inner area of the graph upon which grid lines and points are rendered is called the "grid" area.
+ * Displays graphical data (lines, points, etc.) annotated with domain and range tick markers. The
+ * inner area of the graph upon which grid lines and points are rendered is called the "grid" area.
*/
public class XYGraphWidget extends Widget {
@@ -47,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
*/
@@ -121,18 +134,23 @@ public class XYGraphWidget extends Widget {
/**
* Set of edges for which line labels should be displayed
*/
- private Set lineLabelEdges = new HashSet<>();
+ private EnumSet lineLabelEdges = EnumSet.noneOf(Edge.class);
private RenderStack extends XYSeries, ? extends XYSeriesFormatter> renderStack;
private CursorLabelFormatter cursorLabelFormatter;
- private HashMap lineLabelStyles = getDefaultLineLabelStyles();
- private HashMap lineLabelRenderers = getDefaultLineLabelRenderers();
+ private Map lineLabelStyles = getDefaultLineLabelStyles();
+ private Map lineLabelRenderers = getDefaultLineLabelRenderers();
public static class LineLabelRenderer {
- public void drawLabel(Canvas canvas, LineLabelStyle style, Number val, float x, float y, boolean isOrigin) {
+ public void drawLabel(Canvas canvas,
+ LineLabelStyle style,
+ Number val,
+ float x,
+ float y,
+ boolean isOrigin) {
final int canvasState = canvas.save();
try {
final String txt = style.format.format(val);
@@ -143,7 +161,12 @@ public void drawLabel(Canvas canvas, LineLabelStyle style, Number val, float x,
}
}
- protected void drawLabel(Canvas canvas, String text, Paint paint, float x, float y, boolean isOrigin) {
+ protected void drawLabel(Canvas canvas,
+ String text,
+ Paint paint,
+ float x,
+ float y,
+ boolean isOrigin) {
canvas.drawText(text, x, y, paint);
}
}
@@ -159,6 +182,7 @@ public static class LineLabelStyle {
paint.setTextAlign(Paint.Align.CENTER);
paint.setTextSize(DEFAULT_LINE_LABEL_TEXT_SIZE_PX);
}
+
public Format getFormat() {
return format;
}
@@ -187,17 +211,16 @@ public void setPaint(Paint paint) {
public interface CursorLabelFormatter {
/**
- *
* @return The Paint to be used to draw the cursor text label.
*/
Paint getTextPaint();
/**
- *
- * @return Null if no background should be drawn,
- * the Paint used to draw the background otherwise.
+ * @return Null if no background should be drawn, the Paint used to draw the background
+ * otherwise.
*/
Paint getBackgroundPaint();
+
String getLabelText(Number x, Number y);
}
@@ -256,18 +279,21 @@ public XYGraphWidget(LayoutManager layoutManager, XYPlot plot, Size size) {
/**
* Apply xml attrs
+ *
* @param attrs
*/
public void processAttrs(TypedArray attrs) {
setDrawGridOnTop(attrs.getBoolean(R.styleable.xy_XYPlot_drawGridOnTop, isDrawGridOnTop()));
int tlp = attrs.getInt(R.styleable.xy_XYPlot_lineLabels, 0);
- if(tlp != 0) {
+ if (tlp != 0) {
setLineLabelEdges(tlp);
}
- setGridClippingEnabled(attrs.getBoolean(R.styleable.xy_XYPlot_gridClippingEnabled,
- isGridClippingEnabled()));
+ setGridClippingEnabled(attrs.getBoolean(
+ R.styleable.xy_XYPlot_gridClippingEnabled,
+ isGridClippingEnabled()
+ ));
final LineLabelStyle lineLabelStyleTop = getLineLabelStyle(Edge.TOP);
final LineLabelStyle lineLabelStyleBottom = getLineLabelStyle(Edge.BOTTOM);
@@ -276,19 +302,23 @@ public void processAttrs(TypedArray attrs) {
lineLabelStyleTop.setRotation(attrs.getFloat(
R.styleable.xy_XYPlot_lineLabelRotationTop,
- lineLabelStyleTop.getRotation()));
+ lineLabelStyleTop.getRotation()
+ ));
lineLabelStyleBottom.setRotation(attrs.getFloat(
R.styleable.xy_XYPlot_lineLabelRotationBottom,
- lineLabelStyleBottom.getRotation()));
+ lineLabelStyleBottom.getRotation()
+ ));
lineLabelStyleLeft.setRotation(attrs.getFloat(
R.styleable.xy_XYPlot_lineLabelRotationLeft,
- lineLabelStyleLeft.getRotation()));
+ lineLabelStyleLeft.getRotation()
+ ));
lineLabelStyleRight.setRotation(attrs.getFloat(
R.styleable.xy_XYPlot_lineLabelRotationRight,
- lineLabelStyleRight.getRotation()));
+ lineLabelStyleRight.getRotation()
+ ));
setLineExtensionTop(attrs.getDimension(
R.styleable.xy_XYPlot_lineExtensionTop, getLineExtensionTop()));
@@ -302,34 +332,40 @@ public void processAttrs(TypedArray attrs) {
AttrUtils.configureTextPaint(attrs, lineLabelStyleTop.getPaint(),
R.styleable.xy_XYPlot_lineLabelTextColorTop,
R.styleable.xy_XYPlot_lineLabelTextSizeTop,
- R.styleable.xy_XYPlot_lineLabelAlignTop);
+ R.styleable.xy_XYPlot_lineLabelAlignTop
+ );
AttrUtils.configureTextPaint(attrs, lineLabelStyleBottom.getPaint(),
R.styleable.xy_XYPlot_lineLabelTextColorBottom,
R.styleable.xy_XYPlot_lineLabelTextSizeBottom,
- R.styleable.xy_XYPlot_lineLabelAlignBottom);
+ R.styleable.xy_XYPlot_lineLabelAlignBottom
+ );
AttrUtils.configureTextPaint(attrs, lineLabelStyleLeft.getPaint(),
R.styleable.xy_XYPlot_lineLabelTextColorLeft,
R.styleable.xy_XYPlot_lineLabelTextSizeLeft,
- R.styleable.xy_XYPlot_lineLabelAlignLeft);
+ R.styleable.xy_XYPlot_lineLabelAlignLeft
+ );
AttrUtils.configureTextPaint(attrs, lineLabelStyleRight.getPaint(),
R.styleable.xy_XYPlot_lineLabelTextColorRight,
R.styleable.xy_XYPlot_lineLabelTextSizeRight,
- R.styleable.xy_XYPlot_lineLabelAlignRight);
+ R.styleable.xy_XYPlot_lineLabelAlignRight
+ );
AttrUtils.configureInsets(attrs, getGridInsets(),
R.styleable.xy_XYPlot_gridInsetTop,
R.styleable.xy_XYPlot_gridInsetBottom,
R.styleable.xy_XYPlot_gridInsetLeft,
- R.styleable.xy_XYPlot_gridInsetRight);
+ R.styleable.xy_XYPlot_gridInsetRight
+ );
AttrUtils.configureInsets(attrs, getLineLabelInsets(),
R.styleable.xy_XYPlot_lineLabelInsetTop,
R.styleable.xy_XYPlot_lineLabelInsetBottom,
R.styleable.xy_XYPlot_lineLabelInsetLeft,
- R.styleable.xy_XYPlot_lineLabelInsetRight);
+ R.styleable.xy_XYPlot_lineLabelInsetRight
+ );
// graph size & position
AttrUtils.configureWidget(attrs, this,
@@ -337,7 +373,8 @@ public void processAttrs(TypedArray attrs) {
R.styleable.xy_XYPlot_graphWidthMode, R.styleable.xy_XYPlot_graphWidth,
R.styleable.xy_XYPlot_graphHorizontalPositioning, R.styleable.xy_XYPlot_graphHorizontalPosition,
R.styleable.xy_XYPlot_graphVerticalPositioning, R.styleable.xy_XYPlot_graphVerticalPosition,
- R.styleable.xy_XYPlot_graphAnchor, R.styleable.xy_XYPlot_graphVisible);
+ R.styleable.xy_XYPlot_graphAnchor, R.styleable.xy_XYPlot_graphVisible
+ );
// domainLabel size & position
AttrUtils.configureWidget(attrs, this,
@@ -345,7 +382,8 @@ public void processAttrs(TypedArray attrs) {
R.styleable.xy_XYPlot_domainTitleWidthMode, R.styleable.xy_XYPlot_domainTitleWidth,
R.styleable.xy_XYPlot_domainTitleHorizontalPositioning, R.styleable.xy_XYPlot_domainTitleHorizontalPosition,
R.styleable.xy_XYPlot_domainTitleVerticalPositioning, R.styleable.xy_XYPlot_domainTitleVerticalPosition,
- R.styleable.xy_XYPlot_domainTitleAnchor, R.styleable.xy_XYPlot_domainTitleVisible);
+ R.styleable.xy_XYPlot_domainTitleAnchor, R.styleable.xy_XYPlot_domainTitleVisible
+ );
// rangeLabel size & position
AttrUtils.configureWidget(attrs, this,
@@ -353,7 +391,8 @@ public void processAttrs(TypedArray attrs) {
R.styleable.xy_XYPlot_rangeTitleWidthMode, R.styleable.xy_XYPlot_rangeTitleWidth,
R.styleable.xy_XYPlot_rangeTitleHorizontalPositioning, R.styleable.xy_XYPlot_rangeTitleHorizontalPosition,
R.styleable.xy_XYPlot_rangeTitleVerticalPositioning, R.styleable.xy_XYPlot_rangeTitleVerticalPosition,
- R.styleable.xy_XYPlot_rangeTitleAnchor, R.styleable.xy_XYPlot_rangeTitleVisible);
+ R.styleable.xy_XYPlot_rangeTitleAnchor, R.styleable.xy_XYPlot_rangeTitleVisible
+ );
// rotation
AttrUtils.configureWidgetRotation(attrs, this, R.styleable.xy_XYPlot_graphRotation);
@@ -363,31 +402,38 @@ public void processAttrs(TypedArray attrs) {
R.styleable.xy_XYPlot_graphMarginTop, R.styleable.xy_XYPlot_graphMarginBottom,
R.styleable.xy_XYPlot_graphMarginLeft, R.styleable.xy_XYPlot_graphMarginRight,
R.styleable.xy_XYPlot_graphPaddingTop, R.styleable.xy_XYPlot_graphPaddingBottom,
- R.styleable.xy_XYPlot_graphPaddingLeft, R.styleable.xy_XYPlot_graphPaddingRight);
+ R.styleable.xy_XYPlot_graphPaddingLeft, R.styleable.xy_XYPlot_graphPaddingRight
+ );
// domainOriginLinePaint
AttrUtils.configureLinePaint(attrs, getDomainOriginLinePaint(),
R.styleable.xy_XYPlot_domainOriginLineColor,
- R.styleable.xy_XYPlot_domainOriginLineThickness);
+ R.styleable.xy_XYPlot_domainOriginLineThickness
+ );
// rangeOriginLinePaint
AttrUtils.configureLinePaint(attrs, getRangeOriginLinePaint(),
R.styleable.xy_XYPlot_rangeOriginLineColor,
- R.styleable.xy_XYPlot_rangeOriginLineThickness);
+ R.styleable.xy_XYPlot_rangeOriginLineThickness
+ );
AttrUtils.configureLinePaint(attrs, getDomainGridLinePaint(),
R.styleable.xy_XYPlot_domainLineColor,
- R.styleable.xy_XYPlot_domainLineThickness);
+ R.styleable.xy_XYPlot_domainLineThickness
+ );
AttrUtils.configureLinePaint(attrs, getRangeGridLinePaint(),
R.styleable.xy_XYPlot_rangeLineColor,
- R.styleable.xy_XYPlot_rangeLineThickness);
+ R.styleable.xy_XYPlot_rangeLineThickness
+ );
AttrUtils.setColor(attrs, getBackgroundPaint(),
- R.styleable.xy_XYPlot_graphBackgroundColor);
+ R.styleable.xy_XYPlot_graphBackgroundColor
+ );
AttrUtils.setColor(attrs, getGridBackgroundPaint(),
- R.styleable.xy_XYPlot_gridBackgroundColor);
+ R.styleable.xy_XYPlot_gridBackgroundColor
+ );
}
/**
@@ -399,7 +445,7 @@ public void processAttrs(TypedArray attrs) {
* @return
*/
protected XYCoords screenToSeries(PointF point) {
- if(!plot.getBounds().isFullyDefined()) {
+ if (!plot.getBounds().isFullyDefined()) {
return null;
}
return new RectRegion(gridRect)
@@ -450,6 +496,7 @@ protected Number screenToSeriesX(float xPix) {
* Converts a y pixel to a y value.
* This is a relatively slow operation and should not be used for operations that are a part of
* the main render loop of a dynamic plot.
+ *
* @param yPix
* @return
*/
@@ -462,7 +509,7 @@ protected Number screenToSeriesY(float yPix) {
}
protected PointF seriesToScreen(XYCoords xy) {
- if(!plot.getBounds().isFullyDefined()) {
+ if (!plot.getBounds().isFullyDefined()) {
return null;
}
return plot.getBounds().transform(xy, gridRect, false, true);
@@ -475,20 +522,24 @@ protected float seriesToScreenX(Number x) {
protected float seriesToScreenY(Number y) {
return (float) plot.getBounds().getyRegion().
- transform(y.doubleValue(), gridRect.left, gridRect.right, true);
+ transform(y.doubleValue(), gridRect.bottom, gridRect.top, true);
}
@Override
- protected void doOnDraw(Canvas canvas, RectF widgetRect)
- throws PlotRenderException {
+ protected void onResize(@Nullable RectF oldRect, @NonNull RectF newRect) {
+ recalculateSizes(newRect);
+ }
- if(gridRect == null) {
- gridRect = RectFUtils.applyInsets(widgetRect, gridInsets);
+ protected void recalculateSizes(@Nullable RectF rect) {
+ if(rect == null) {
+ rect = getWidgetDimensions().paddedRect;
}
+ gridRect = RectFUtils.applyInsets(rect, gridInsets);
+ labelRect = RectFUtils.applyInsets(rect, lineLabelInsets);
+ }
- if(labelRect == null) {
- labelRect = RectFUtils.applyInsets(widgetRect, lineLabelInsets);
- }
+ @Override
+ 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) {
@@ -497,7 +548,7 @@ protected void doOnDraw(Canvas canvas, RectF widgetRect)
&& bounds.getMaxX() != null
&& bounds.getMinY() != null
&& bounds.getMaxY() != null) {
- if(drawGridOnTop) {
+ if (drawGridOnTop) {
drawData(canvas);
drawGrid(canvas);
} else {
@@ -513,36 +564,54 @@ protected void doOnDraw(Canvas canvas, RectF widgetRect)
}
protected void drawDomainLine(Canvas canvas, float xPix, Number xVal,
- Paint linePaint, boolean isOrigin) {
+ Paint linePaint, boolean isOrigin, boolean shouldDrawLabel) {
// lines
if (linePaint != null) {
- canvas.drawLine(xPix, gridRect.top - lineExtensionTop,
- xPix, gridRect.bottom + lineExtensionBottom, linePaint);
+ canvas.drawLine(xPix, gridRect.top - lineExtensionTop,
+ xPix, gridRect.bottom + lineExtensionBottom, linePaint
+ );
}
// labels
- drawLineLabel(canvas, Edge.TOP, xVal, xPix, labelRect.top, isOrigin);
- drawLineLabel(canvas, Edge.BOTTOM, xVal, xPix, labelRect.bottom, isOrigin);
+ if(shouldDrawLabel) {
+ if (isLineLabelEnabled(Edge.TOP)) {
+ drawLineLabel(canvas, Edge.TOP, xVal, xPix, labelRect.top, isOrigin);
+ }
+
+ if (isLineLabelEnabled(Edge.BOTTOM)) {
+ drawLineLabel(canvas, Edge.BOTTOM, xVal, xPix, labelRect.bottom, isOrigin);
+ }
+ }
}
protected void drawRangeLine(Canvas canvas, float yPix, Number yVal,
- Paint linePaint, boolean isOrigin) {
+ Paint linePaint, boolean isOrigin, boolean shouldDrawLabel) {
// lines
if (linePaint != null) {
canvas.drawLine(gridRect.left - lineExtensionLeft, yPix,
- gridRect.right + lineExtensionRight, yPix, linePaint);
+ gridRect.right + lineExtensionRight, yPix, linePaint
+ );
}
- // labels
- drawLineLabel(canvas, Edge.LEFT, yVal, labelRect.left, yPix, isOrigin);
- drawLineLabel(canvas, Edge.RIGHT, yVal, labelRect.right, yPix, isOrigin);
+ if(shouldDrawLabel) {
+ // labels
+ if (isLineLabelEnabled(Edge.LEFT)) {
+ drawLineLabel(canvas, Edge.LEFT, yVal, labelRect.left, yPix, isOrigin);
+ }
+ if (isLineLabelEnabled(Edge.RIGHT)) {
+ drawLineLabel(canvas, Edge.RIGHT, yVal, labelRect.right, yPix, isOrigin);
+ }
+ }
}
- protected void drawLineLabel(Canvas canvas, Edge edge, Number val, float x, float y, boolean isOrigin) {
- if(isLineLabelEnabled(edge)) {
- getLineLabelRenderer(edge).drawLabel(canvas, getLineLabelStyle(edge), val, x, y, isOrigin);
- }
+ protected void drawLineLabel(Canvas canvas,
+ Edge edge,
+ Number val,
+ float x,
+ float y,
+ boolean isOrigin) {
+ getLineLabelRenderer(edge).drawLabel(canvas, getLineLabelStyle(edge), val, x, y, isOrigin);
}
/**
@@ -551,63 +620,48 @@ protected void drawLineLabel(Canvas canvas, Edge edge, Number val, float x, floa
* @param canvas
*/
protected void drawGrid(Canvas canvas) {
- if(!drawGridOnTop) {
+ if (!drawGridOnTop) {
drawGridBackground(canvas);
}
Number domainOrigin = plot.getDomainOrigin();
- double domainOriginPix;
+ final double domainOriginPix;
if (domainOrigin != null) {
domainOriginPix = plot.getBounds().getxRegion().transform(
plot.getDomainOrigin().doubleValue(), gridRect.left, gridRect.right, false);
} else {
// if no domain origin is set, use the leftmost value visible on the grid:
domainOriginPix = gridRect.left;
- domainOrigin=plot.getBounds().getMinX();
+ domainOrigin = plot.getBounds().getMinX();
}
Step domainStep = XYStepCalculator.getStep(plot, Axis.DOMAIN, gridRect);
- // draw domain origin:
- if (domainOriginPix >= gridRect.left
- && domainOriginPix <= gridRect.right) {
- drawDomainLine(canvas, (float) domainOriginPix,
- domainOrigin, domainOriginLinePaint, true);
- }
-
- // draw lines LEFT of origin:
- double xPix = domainOriginPix - domainStep.getStepPix();
- for (int i = ONE; xPix >= gridRect.left - FUDGE; xPix = domainOriginPix
- - (i * domainStep.getStepPix())) {
- double xVal = domainOrigin.doubleValue() - i
- * domainStep.getStepVal();
-
- if (xPix <= gridRect.right) {
- final boolean isDomainTick = i% getLinesPerDomainLabel() == ZERO;
- final Paint lp = isDomainTick ? domainGridLinePaint : domainSubGridLinePaint;
- drawDomainLine(canvas, (float) xPix, xVal, lp, false);
+ // Draw Domain Lines:
+
+ final double domainStepPix = domainStep.getStepPix();
+ final double iMin = (gridRect.left - domainOriginPix - FUDGE) / domainStepPix;
+ final double iMax = (gridRect.right - domainOriginPix + FUDGE) / domainStepPix;
+
+ for (int i = (int) Math.ceil(iMin); i <= iMax; i++) {
+ double xVal = domainOrigin.doubleValue() + i * domainStep.getStepVal();
+ double xPix = domainOriginPix + i * domainStepPix;
+ boolean isMajorTick = i % getLinesPerDomainLabel() == ZERO;
+ boolean isOrigin = i == 0;
+ Paint linePaint;
+ if (isOrigin) {
+ linePaint = domainOriginLinePaint;
+ } else if (isMajorTick) {
+ linePaint = domainGridLinePaint;
+ } else {
+ linePaint = domainSubGridLinePaint;
}
- i++;
- }
-
- // draw lines RIGHT of origin:
- xPix = domainOriginPix + domainStep.getStepPix();
- for (int i = ONE; xPix <= gridRect.right + FUDGE; xPix = domainOriginPix
- + (i * domainStep.getStepPix())) {
- double xVal = domainOrigin.doubleValue() + i
- * domainStep.getStepVal();
-
- if (xPix >= gridRect.left) {
- final boolean isDomainTick = i% getLinesPerDomainLabel() == ZERO;
- final Paint lp = isDomainTick ? domainGridLinePaint : domainSubGridLinePaint;
- drawDomainLine(canvas, (float) xPix, xVal, lp, false);
- }
- i++;
+ drawDomainLine(canvas, (float) xPix, xVal, linePaint, isOrigin, isMajorTick);
}
Number rangeOrigin = plot.getRangeOrigin();
- double rangeOriginPix;
+ final double rangeOriginPix;
if (rangeOrigin != null) {
rangeOriginPix = plot.getBounds().getyRegion().transform(
rangeOrigin.doubleValue(), gridRect.top, gridRect.bottom, true);
@@ -619,106 +673,42 @@ protected void drawGrid(Canvas canvas) {
Step rangeStep = XYStepCalculator.getStep(plot, Axis.RANGE, gridRect);
- // draw range origin:
- if (rangeOriginPix >= gridRect.top && rangeOriginPix <= gridRect.bottom) {
- drawRangeLine(canvas, (float) rangeOriginPix,
- rangeOrigin, rangeOriginLinePaint, true);
- }
+ // Draw Range Lines:
final double rangeStepPix = rangeStep.getStepPix();
-
- // draw lines ABOVE origin:
- double yPix = rangeOriginPix - rangeStep.getStepPix();
- for (int i = ONE; yPix >= gridRect.top - FUDGE; yPix = rangeOriginPix - (i * rangeStepPix)) {
- double yVal = rangeOrigin.doubleValue() + i
- * rangeStep.getStepVal();
-
- if (yPix <= gridRect.bottom) {
- final boolean isRangeTick = i% getLinesPerRangeLabel() == ZERO;
- final Paint lp = isRangeTick ? rangeGridLinePaint : rangeSubGridLinePaint;
- drawRangeLine(canvas, (float)yPix, yVal, lp, false);
+ final double kMin = (gridRect.top - rangeOriginPix - FUDGE) / rangeStepPix;
+ final double kMax = (gridRect.bottom - rangeOriginPix + FUDGE) / rangeStepPix;
+
+ for (int k = (int) Math.ceil(kMin); k <= kMax; k++) {
+ // Android vertical coordinates (zero at the top of the screen) are the opposite
+ // direction of default range values (lowest on bottom of screen) so we subtract when
+ // calculating yVal
+ double yVal = rangeOrigin.doubleValue() - k * rangeStep.getStepVal();
+ double yPix = rangeOriginPix + k * rangeStepPix;
+ boolean isMajorTick = k % getLinesPerRangeLabel() == ZERO;
+ boolean isOrigin = k == 0;
+ Paint linePaint;
+ if (isOrigin) {
+ linePaint = rangeOriginLinePaint;
+ } else if (isMajorTick) {
+ linePaint = rangeGridLinePaint;
+ } else {
+ linePaint = rangeSubGridLinePaint;
}
- i++;
+ drawRangeLine(canvas, (float) yPix, yVal, linePaint, isOrigin, isMajorTick);
}
-
- // draw lines BENEATH origin:
- yPix = rangeOriginPix + rangeStep.getStepPix();
- for (int i = ONE; yPix <= gridRect.bottom + FUDGE; yPix = rangeOriginPix + (i * rangeStepPix)) {
- double yVal = rangeOrigin.doubleValue() - i
- * rangeStep.getStepVal();
- if (yPix >= gridRect.top) {
- final boolean isRangeTick = i% getLinesPerRangeLabel() == ZERO;
- final Paint lp = isRangeTick ? rangeGridLinePaint : rangeSubGridLinePaint;
- drawRangeLine(canvas, (float)yPix, yVal, lp, false);
- }
- i++;
- }
- }
-
- /**
- * Renders the text associated with user defined markers
- *
- * @param canvas
- * @param text
- * @param marker
- * @param x
- * @param y
- */
- private void drawMarkerText(Canvas canvas, String text, ValueMarker marker,
- float x, float y) {
- 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) {
+ 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;
-
- if (marker.getText() != null) {
- drawMarkerText(canvas, marker.getText(), marker, xPix, yPix);
- }
- }
+ marker.draw(canvas, plot, gridRect);
}
}
- if(plot.getXValueMarkers() != null && plot.getXValueMarkers().size() > 0) {
+ 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);
}
}
}
@@ -733,7 +723,8 @@ protected void drawCursors(Canvas canvas) {
hasDomainCursor = true;
canvas.drawLine(domainCursorPosition, gridRect.top,
domainCursorPosition, gridRect.bottom,
- domainCursorPaint);
+ domainCursorPaint
+ );
}
boolean hasRangeCursor = false;
@@ -744,10 +735,11 @@ protected void drawCursors(Canvas canvas) {
&& rangeCursorPosition <= gridRect.bottom) {
hasRangeCursor = true;
canvas.drawLine(gridRect.left, rangeCursorPosition,
- gridRect.right, rangeCursorPosition, rangeCursorPaint);
+ gridRect.right, rangeCursorPosition, rangeCursorPaint
+ );
}
- if(getCursorLabelFormatter() != null && hasRangeCursor && hasDomainCursor) {
+ if (getCursorLabelFormatter() != null && hasRangeCursor && hasDomainCursor) {
drawCursorLabel(canvas);
}
}
@@ -765,8 +757,10 @@ protected void drawCursorLabel(Canvas canvas) {
// if we are too close to the right edge of the plot, we will move
// the label to the left side of our cursor:
if (cursorRect.right >= gridRect.right) {
- cursorRect.offsetTo(domainCursorPosition - cursorRect.width(),
- cursorRect.top);
+ cursorRect.offsetTo(
+ domainCursorPosition - cursorRect.width(),
+ cursorRect.top
+ );
}
// same thing for the top edge of the plot:
@@ -780,11 +774,12 @@ protected void drawCursorLabel(Canvas canvas) {
}
canvas.drawText(label, cursorRect.left, cursorRect.bottom,
- getCursorLabelFormatter().getTextPaint());
+ getCursorLabelFormatter().getTextPaint()
+ );
}
protected void drawGridBackground(Canvas canvas) {
- if(gridBackgroundPaint != null) {
+ if (gridBackgroundPaint != null) {
canvas.drawRect(gridRect, gridBackgroundPaint);
}
}
@@ -793,22 +788,21 @@ 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);
}
try {
- if(isGridClippingEnabled) {
- canvas.save(Canvas.ALL_SAVE_FLAG);
+ if (isGridClippingEnabled) {
+ canvas.save();
canvas.clipRect(gridRect, android.graphics.Region.Op.INTERSECT);
}
renderStack.sync();
- for(RenderStack.StackElement thisElement : renderStack.getElements()) {
- if(thisElement.isEnabled()) {
+ for (RenderStack.StackElement thisElement : renderStack.getElements()) {
+ if (thisElement.isEnabled()) {
Class extends XYSeriesRenderer> rendererClass =
thisElement.get().getFormatter().getRendererClass();
plot.getRenderer(rendererClass).render(
@@ -817,7 +811,7 @@ protected void drawData(Canvas canvas) throws PlotRenderException {
}
} finally {
- if(isGridClippingEnabled) {
+ if (isGridClippingEnabled) {
canvas.restore();
}
}
@@ -844,6 +838,7 @@ public Paint getDomainGridLinePaint() {
/**
* Set the paint used to draw the domain grid line.
+ *
* @param gridLinePaint
*/
public void setDomainGridLinePaint(Paint gridLinePaint) {
@@ -866,6 +861,7 @@ public Paint getDomainSubGridLinePaint() {
/**
* Set the paint used to draw the domain grid line.
+ *
* @param gridLinePaint
*/
public void setDomainSubGridLinePaint(Paint gridLinePaint) {
@@ -874,6 +870,7 @@ public void setDomainSubGridLinePaint(Paint gridLinePaint) {
/**
* Set the Paint used to draw the range grid line.
+ *
* @param gridLinePaint
*/
public void setRangeGridLinePaint(Paint gridLinePaint) {
@@ -889,6 +886,7 @@ public Paint getRangeSubGridLinePaint() {
/**
* Set the Paint used to draw the range grid line.
+ *
* @param gridLinePaint
*/
public void setRangeSubGridLinePaint(Paint gridLinePaint) {
@@ -929,6 +927,7 @@ public void setRangeOriginLinePaint(Paint rangeOriginLinePaint) {
/**
* Set domain and range cursor position using screen coordinates
+ *
* @param x
* @param y
*/
@@ -939,6 +938,7 @@ public void setCursorPosition(Float x, Float y) {
/**
* Set domain and range cursor position using screen coordinates
+ *
* @param point
*/
public void setCursorPosition(PointF point) {
@@ -955,6 +955,7 @@ public Number getDomainCursorVal() {
/**
* Set domain cursor position using screen coordinates
+ *
* @param domainCursorPosition
*/
public void setDomainCursorPosition(Float domainCursorPosition) {
@@ -971,6 +972,7 @@ public Number getRangeCursorVal() {
/**
* Set range cursor position using screen coordinates
+ *
* @param rangeCursorPosition
*/
public void setRangeCursorPosition(Float rangeCursorPosition) {
@@ -998,9 +1000,8 @@ public Paint getDomainCursorPaint() {
}
/**
- *
- * @param domainCursorPaint The {@link Paint} used to draw the domain cursor line.
- * Set to null (default) to disable.
+ * @param domainCursorPaint The {@link Paint} used to draw the domain cursor line. Set to null
+ * (default) to disable.
*/
public void setDomainCursorPaint(Paint domainCursorPaint) {
this.domainCursorPaint = domainCursorPaint;
@@ -1011,9 +1012,8 @@ public Paint getRangeCursorPaint() {
}
/**
- *
- * @param rangeCursorPaint The {@link Paint} used to draw the range cursor line.
- * Set to null (default) to disable.
+ * @param rangeCursorPaint The {@link Paint} used to draw the range cursor line. Set to null
+ * (default) to disable.
*/
public void setRangeCursorPaint(Paint rangeCursorPaint) {
this.rangeCursorPaint = rangeCursorPaint;
@@ -1051,8 +1051,8 @@ public void setLineExtensionRight(float lineExtensionRight) {
this.lineExtensionRight = lineExtensionRight;
}
- protected HashMap getDefaultLineLabelStyles() {
- HashMap defaults = new HashMap<>();
+ protected Map getDefaultLineLabelStyles() {
+ EnumMap defaults = new EnumMap<>(Edge.class);
defaults.put(Edge.TOP, new LineLabelStyle());
defaults.put(Edge.BOTTOM, new LineLabelStyle());
defaults.put(Edge.LEFT, new LineLabelStyle());
@@ -1060,8 +1060,8 @@ protected HashMap getDefaultLineLabelStyles() {
return defaults;
}
- protected HashMap getDefaultLineLabelRenderers() {
- HashMap defaults = new HashMap<>();
+ protected Map getDefaultLineLabelRenderers() {
+ EnumMap defaults = new EnumMap<>(Edge.class);
defaults.put(Edge.TOP, new LineLabelRenderer());
defaults.put(Edge.BOTTOM, new LineLabelRenderer());
defaults.put(Edge.LEFT, new LineLabelRenderer());
@@ -1103,6 +1103,7 @@ public Insets getGridInsets() {
public void setGridInsets(Insets gridInsets) {
this.gridInsets = gridInsets;
+ recalculateSizes(null);
}
/**
@@ -1114,6 +1115,7 @@ public Insets getLineLabelInsets() {
public void setLineLabelInsets(Insets lineLabelInsets) {
this.lineLabelInsets = lineLabelInsets;
+ recalculateSizes(null);
}
public RectF getGridRect() {
@@ -1145,22 +1147,20 @@ public boolean isLineLabelEnabled(Edge position) {
}
public void setLineLabelEdges(Edge... positions) {
- Set positionSet = new HashSet<>();
- if(positions != null) {
- for(Edge position : positions) {
- positionSet.add(position);
- }
+ EnumSet positionSet = EnumSet.noneOf(Edge.class);
+ if (positions != null) {
+ Collections.addAll(positionSet, positions);
}
- setLineLabelEdges(positionSet);
+ this.lineLabelEdges = positionSet;
}
- public void setLineLabelEdges(Set positions) {
- this.lineLabelEdges = positions;
+ public void setLineLabelEdges(Collection positions) {
+ this.lineLabelEdges = EnumSet.copyOf(positions);
}
protected void setLineLabelEdges(int bitfield) {
- for(Edge tp : Edge.values()) {
- if((tp.value & bitfield) == tp.value) {
+ for (Edge tp : Edge.values()) {
+ if ((tp.value & bitfield) == tp.value) {
lineLabelEdges.add(tp);
}
}
diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYLegendItem.java b/androidplot-core/src/main/java/com/androidplot/xy/XYLegendItem.java
new file mode 100644
index 00000000..51c67159
--- /dev/null
+++ b/androidplot-core/src/main/java/com/androidplot/xy/XYLegendItem.java
@@ -0,0 +1,28 @@
+package com.androidplot.xy;
+
+import androidx.annotation.NonNull;
+
+import com.androidplot.ui.widget.LegendItem;
+
+public class XYLegendItem implements LegendItem {
+
+ public enum Type {
+ SERIES,
+ REGION
+ }
+
+ public final Type type;
+ public final Object item;
+ private final String text;
+
+ public XYLegendItem(@NonNull Type cellType, @NonNull Object item, @NonNull String text) {
+ this.type = cellType;
+ this.item = item;
+ this.text = text;
+ }
+
+ @Override
+ public String getTitle() {
+ return this.text;
+ }
+}
diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYLegendWidget.java b/androidplot-core/src/main/java/com/androidplot/xy/XYLegendWidget.java
index e5b981c5..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,233 +17,80 @@
package com.androidplot.xy;
import android.graphics.*;
+import androidx.annotation.NonNull;
+
import com.androidplot.ui.LayoutManager;
import com.androidplot.ui.SeriesBundle;
import com.androidplot.ui.Size;
import com.androidplot.ui.TableModel;
-import com.androidplot.ui.widget.Widget;
-import com.androidplot.util.FontUtils;
+import com.androidplot.ui.widget.LegendWidget;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map.Entry;
/**
* Displays a legend for each series added to the owning {@link XYPlot}.
*/
-public class XYLegendWidget extends Widget {
-
- /**
- * This class is of no use outside of XYLegendWidget. It's just used to alphabetically sort
- * Region legend entries.
- */
- private static class RegionEntryComparator implements Comparator> {
- @Override
- public int compare(Map.Entry o1, Map.Entry o2) {
- return o1.getValue().compareTo(o2.getValue());
- }
- }
-
- private enum CellType {
- SERIES,
- REGION
- }
+public class XYLegendWidget extends LegendWidget {
private XYPlot plot;
- //private float iconWidth = 12;
- private Paint textPaint;
- private Paint iconBorderPaint;
- private TableModel tableModel;
- private boolean drawIconBackgroundEnabled = true;
- private boolean drawIconBorderEnabled = true;
-
- private Size iconSize;
- private static final RegionEntryComparator regionEntryComparator = new RegionEntryComparator();
- //private RectF iconRect = new RectF(0, 0, ICON_WIDTH_DEFAULT, ICON_HEIGHT_DEFAULT);
-
- {
- textPaint = new Paint();
- textPaint.setColor(Color.LTGRAY);
- textPaint.setAntiAlias(true);
-
- iconBorderPaint = new Paint();
- iconBorderPaint.setStyle(Paint.Style.STROKE);
- //regionEntryComparator = new RegionEntryComparator();
- }
public XYLegendWidget(LayoutManager layoutManager, XYPlot plot,
Size widgetSize,
TableModel tableModel,
Size iconSize) {
- super(layoutManager, widgetSize);
+ super(tableModel, layoutManager, widgetSize, iconSize);
this.plot = plot;
- setTableModel(tableModel);
- this.iconSize = iconSize;
- }
-
- public synchronized void setTableModel(TableModel tableModel) {
- this.tableModel = tableModel;
- }
- private RectF getIconRect(RectF cellRect) {
- float cellRectCenterY = cellRect.top + (cellRect.height()/2);
- RectF iconRect = iconSize.getRectF(cellRect);
-
- // center the icon rect vertically
- float centeredIconOriginY = cellRectCenterY - (iconRect.height()/2);
- iconRect.offsetTo(cellRect.left + 1, centeredIconOriginY);
- return iconRect;
- }
-
- private static float getRectCenterY(RectF cellRect) {
- return cellRect.top + (cellRect.height()/2);
- }
-
- private void beginDrawingCell(Canvas canvas, RectF iconRect) {
-
- Paint bgPaint = plot.getGraph().getGridBackgroundPaint();
- if(drawIconBackgroundEnabled && bgPaint != null) {
- canvas.drawRect(iconRect, bgPaint);
- }
- }
-
- private void finishDrawingCell(Canvas canvas, RectF cellRect, RectF iconRect, String text) {
-
- Paint bgPaint = plot.getGraph().getGridBackgroundPaint();
- if(drawIconBorderEnabled && bgPaint != null) {
- iconBorderPaint.setColor(bgPaint.getColor());
- canvas.drawRect(iconRect, iconBorderPaint);
- }
-
- float centeredTextOriginY = getRectCenterY(cellRect) + (FontUtils.getFontHeight(textPaint)/2);
-
- if (textPaint.getTextAlign().equals(Paint.Align.RIGHT)) {
- canvas.drawText(text, iconRect.left - 2, centeredTextOriginY, textPaint);
- } else {
- canvas.drawText(text, iconRect.right + 2, centeredTextOriginY, textPaint);
- }
+ // Set a default comparator that sorts by type and then alphabetically
+ setLegendItemComparator(new Comparator() {
+ @Override
+ public int compare(XYLegendItem o1, XYLegendItem o2) {
+ if(o1.type == o2.type) {
+ return o1.getTitle().compareTo(o2.getTitle());
+ } else {
+ return(o1.type.compareTo(o2.type));
+ }
+ }
+ });
}
protected void drawRegionLegendIcon(Canvas canvas, RectF rect, XYRegionFormatter formatter) {
- canvas.drawRect(rect, formatter.getPaint());
- }
-
- private void drawRegionLegendCell(Canvas canvas, XYRegionFormatter formatter, RectF cellRect, String text) {
- RectF iconRect = getIconRect(cellRect);
- beginDrawingCell(canvas, iconRect);
-
- drawRegionLegendIcon(
- canvas,
- iconRect,
- formatter
- );
- finishDrawingCell(canvas, cellRect, iconRect, text);
+ canvas.drawRect(rect, formatter.getPaint());
}
- private void drawSeriesLegendCell(Canvas canvas, XYSeriesRenderer renderer, XYSeriesFormatter formatter, RectF cellRect, String seriesTitle) {
- RectF iconRect = getIconRect(cellRect);
- beginDrawingCell(canvas, iconRect);
-
- renderer.drawSeriesLegendIcon(
- canvas,
- iconRect,
- formatter);
- finishDrawingCell(canvas, cellRect, iconRect, seriesTitle);
+ @Override
+ protected void drawIcon(@NonNull Canvas canvas, @NonNull RectF iconRect, @NonNull XYLegendItem XYLegendItem) {
+ switch (XYLegendItem.type) {
+ case REGION:
+ drawRegionLegendIcon(canvas, iconRect, (XYRegionFormatter) XYLegendItem.item);
+ break;
+ case SERIES:
+ final XYSeriesFormatter formatter = (XYSeriesFormatter) XYLegendItem.item;
+ plot.getRenderer(formatter.getRendererClass()).drawSeriesLegendIcon(canvas, iconRect, formatter);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unexpected item type: " + XYLegendItem.type);
+ }
}
-// protected List> getLegendEnabledSeriesAndFormatterList() {
-// List> sfList = new ArrayList<>();
-// ListIterator> it = plot.getSeriesRegistry().listIterator();
-// while(it.hasNext()) {
-// SeriesAndFormatter thisSf = it.next();
-// if(thisSf.getFormatter().isLegendIconEnabled()) {
-// sfList.add(thisSf);
-// }
-// }
-// return sfList;
-// }
-
@Override
- protected synchronized void doOnDraw(Canvas canvas, RectF widgetRect) {
- if(plot.isEmpty()) {
- return;
+ protected List getLegendItems() {
+ final ArrayList items = new ArrayList<>();
+ for (SeriesBundle sfPair : plot.getRegistry().getLegendEnabledItems()) {
+ items.add(new XYLegendItem(XYLegendItem.Type.SERIES, sfPair.getFormatter(), sfPair.getSeries().getTitle()));
}
- // Keep an alphabetically sorted list of regions:
- TreeSet> sortedRegions = new TreeSet>(new RegionEntryComparator());
-
- // Calculate the number of cells needed to draw the Legend:
- int seriesCount = plot.getRegistry().size();
-
- for(XYSeriesRenderer renderer : plot.getRendererList()) {
+ for (XYSeriesRenderer renderer : plot.getRendererList()) {
Hashtable urf = renderer.getUniqueRegionFormatters();
- sortedRegions.addAll(urf.entrySet());
- }
-
- seriesCount += sortedRegions.size();
-
- // Create an iterator specially created to draw the number of cells we calculated:
- Iterator it = tableModel.getIterator(widgetRect, seriesCount);
-
- RectF cellRect;
-
- // draw each series legend item:
- for(SeriesBundle sfPair : plot.getRegistry().getLegendEnabledItems()) {
- //for(SeriesAndFormatter sfPair : plot.getSeriesRegistry()) {
- cellRect = it.next();
- XYSeriesFormatter format = sfPair.getFormatter();
- drawSeriesLegendCell(canvas, plot.getRenderer(sfPair.getFormatter().getRendererClass()),
- format, cellRect, sfPair.getSeries().getTitle());
- }
-
- // draw each region legend item:
- for(Map.Entry entry : sortedRegions) {
- if(!it.hasNext()) {
- break;
+ for (Entry entry : urf.entrySet()) {
+ items.add(new XYLegendItem(XYLegendItem.Type.REGION, entry.getKey(), entry.getValue()));
}
- cellRect = it.next();
- XYRegionFormatter formatter = entry.getKey();
- drawRegionLegendCell(canvas, formatter, cellRect, entry.getValue());
}
- }
-
-
- public Paint getTextPaint() {
- return textPaint;
- }
-
- public void setTextPaint(Paint textPaint) {
- this.textPaint = textPaint;
- }
-
- public boolean isDrawIconBackgroundEnabled() {
- return drawIconBackgroundEnabled;
- }
-
- public void setDrawIconBackgroundEnabled(boolean drawIconBackgroundEnabled) {
- this.drawIconBackgroundEnabled = drawIconBackgroundEnabled;
- }
-
- public boolean isDrawIconBorderEnabled() {
- return drawIconBorderEnabled;
- }
-
- public void setDrawIconBorderEnabled(boolean drawIconBorderEnabled) {
- this.drawIconBorderEnabled = drawIconBorderEnabled;
- }
-
- public TableModel getTableModel() {
- return tableModel;
- }
-
- public Size getIconSize() {
- return iconSize;
- }
- /**
- * Set the size of each legend's icon. Note that when using relative sizing,
- * the size is calculated against the countaining cell's size, not the plot's size.
- * @param iconSize
- */
- public void setIconSize(Size iconSize) {
- this.iconSize = iconSize;
+ return items;
}
}
diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java b/androidplot-core/src/main/java/com/androidplot/xy/XYPlot.java
index 33769886..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,12 +22,18 @@
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.*;
-import com.androidplot.ui.*;
+import com.androidplot.Plot;
+import com.androidplot.R;
+import com.androidplot.ui.Anchor;
+import com.androidplot.ui.DynamicTableModel;
+import com.androidplot.ui.HorizontalPositioning;
+import com.androidplot.ui.Size;
+import com.androidplot.ui.SizeMode;
import com.androidplot.ui.TextOrientation;
+import com.androidplot.ui.VerticalPositioning;
import com.androidplot.ui.widget.TextLabelWidget;
import com.androidplot.util.AttrUtils;
import com.androidplot.util.PixelUtils;
@@ -42,9 +48,6 @@
*/
public class XYPlot extends Plot {
- private static final int DEFAULT_LEGEND_WIDGET_H_DP = 10;
- private static final int DEFAULT_LEGEND_WIDGET_ICON_SIZE_DP = 7;
-
private static final int DEFAULT_GRAPH_WIDGET_H_DP = 18;
private static final int DEFAULT_GRAPH_WIDGET_W_DP = 10;
@@ -54,8 +57,11 @@ public class XYPlot extends Plot min.doubleValue()))
? value
: min);
@@ -678,7 +684,7 @@ protected Number[] getOriginMinMax(BoundaryMode mode, Number origin, Number exte
* @param y
* @return
*/
- private double distance(double x, double y) {
+ private static double distance(double x, double y) {
if (x > y) {
return x - y;
} else {
@@ -688,15 +694,15 @@ private double distance(double x, double y) {
public void updateDomainMinMaxForOriginModel() {
double origin = userDomainOrigin.doubleValue();
- double maxXDelta = distance(bounds.getMaxX().doubleValue(), origin);
- double minXDelta = distance(bounds.getMinX().doubleValue(), origin);
- double delta = maxXDelta > minXDelta ? maxXDelta : minXDelta;
- double dlb = origin - delta;
- double dub = origin + delta;
+ double maxDelta = distance(bounds.getMaxX().doubleValue(), origin);
+ double minDelta = distance(bounds.getMinX().doubleValue(), origin);
+ double delta = maxDelta > minDelta ? maxDelta : minDelta;
+ double lowerBoundary = origin - delta;
+ double upperBoundary = origin + delta;
switch (domainOriginBoundaryMode) {
case AUTO:
- bounds.setMinX(dlb);
- bounds.setMaxX(dub);
+ bounds.setMinX(lowerBoundary);
+ bounds.setMaxX(upperBoundary);
break;
// if fixed, then the value already exists within "user" vals.
@@ -704,28 +710,28 @@ public void updateDomainMinMaxForOriginModel() {
break;
case GROW: {
- if (prevMinX == null || dlb < prevMinX.doubleValue()) {
- bounds.setMinX(dlb);
+ if (prevMinX == null || lowerBoundary < prevMinX.doubleValue()) {
+ bounds.setMinX(lowerBoundary);
} else {
bounds.setMinX(prevMinX);
}
- if (prevMaxX == null || dub > prevMaxX.doubleValue()) {
- bounds.setMaxX(dub);
+ if (prevMaxX == null || upperBoundary > prevMaxX.doubleValue()) {
+ bounds.setMaxX(upperBoundary);
} else {
bounds.setMaxX(prevMaxX);
}
}
break;
case SHRINK:
- if (prevMinX == null || dlb > prevMinX.doubleValue()) {
- bounds.setMinX(dlb);
+ if (prevMinX == null || lowerBoundary > prevMinX.doubleValue()) {
+ bounds.setMinX(lowerBoundary);
} else {
bounds.setMinX(prevMinX);
}
- if (prevMaxX == null || dub < prevMaxX.doubleValue()) {
- bounds.setMaxX(dub);
+ if (prevMaxX == null || upperBoundary < prevMaxX.doubleValue()) {
+ bounds.setMaxX(upperBoundary);
} else {
bounds.setMaxX(prevMaxX);
}
@@ -739,14 +745,14 @@ public void updateRangeMinMaxForOriginModel() {
switch (rangeOriginBoundaryMode) {
case AUTO:
double origin = userRangeOrigin.doubleValue();
- double maxYDelta = distance(bounds.getMaxY().doubleValue(), origin);
- double minYDelta = distance(bounds.getMinY().doubleValue(), origin);
- if (maxYDelta > minYDelta) {
- bounds.setMinY(origin - maxYDelta);
- bounds.setMaxY(origin + maxYDelta);
+ double maxDelta = distance(bounds.getMaxY().doubleValue(), origin);
+ double minDelta = distance(bounds.getMinY().doubleValue(), origin);
+ if (maxDelta > minDelta) {
+ bounds.setMinY(origin - maxDelta);
+ bounds.setMaxY(origin + maxDelta);
} else {
- bounds.setMinY(origin - minYDelta);
- bounds.setMaxY(origin + minYDelta);
+ bounds.setMinY(origin - minDelta);
+ bounds.setMaxY(origin + minDelta);
}
break;
case FIXED:
diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYRegionFormatter.java b/androidplot-core/src/main/java/com/androidplot/xy/XYRegionFormatter.java
index b0a3186f..0492c6c2 100644
--- a/androidplot-core/src/main/java/com/androidplot/xy/XYRegionFormatter.java
+++ b/androidplot-core/src/main/java/com/androidplot/xy/XYRegionFormatter.java
@@ -43,7 +43,11 @@ public class XYRegionFormatter {
public XYRegionFormatter(Context ctx, int xmlCfgId) {
// prevent configuration of classes derived from this one:
if (getClass().equals(XYRegionFormatter.class)) {
- Fig.configure(ctx, this, xmlCfgId);
+ try {
+ Fig.configure(ctx, this, xmlCfgId);
+ } catch (FigException e) {
+ throw new RuntimeException(e);
+ }
}
}
diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYSeriesFormatter.java b/androidplot-core/src/main/java/com/androidplot/xy/XYSeriesFormatter.java
index 0c18d2e9..27924ab1 100644
--- a/androidplot-core/src/main/java/com/androidplot/xy/XYSeriesFormatter.java
+++ b/androidplot-core/src/main/java/com/androidplot/xy/XYSeriesFormatter.java
@@ -16,7 +16,7 @@
package com.androidplot.xy;
-import android.content.*;
+import android.content.Context;
import com.androidplot.ui.Formatter;
import com.androidplot.util.LayerHash;
@@ -28,7 +28,7 @@ public abstract class XYSeriesFormatter
-
+
+
-
+
@@ -48,36 +60,35 @@
-
+
-
-
-
+
+
+
-
+
-
+
-
+
-
+
@@ -190,7 +201,7 @@
-
+
@@ -267,180 +278,857 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
+
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
+
+
+
-
-
+
+
+
+
+
+Enable line labels on one or more edge of the graph. For example, to enable labels on the left
+and bottom edges:
+```
+ap:lineLabels="left|bottom"
+```
+-->
+
+Text alignment of line labels drawn on top edge of the plot. This alignment is applied relative
+to the line label insets defined by `lineLabelInsetTop`.
+-->
+
+
-
+
+Text alignment of line labels drawn on right edge of the plot. This alignment is applied relative
+to the line label insets defined by `lineLabelInsetRight`.
+-->
+
+
+
+
+
-
+
+
+
+
+
+
+
+
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
-
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
+
\ No newline at end of file
diff --git a/androidplot-core/src/test/java/com/androidplot/PlotTest.java b/androidplot-core/src/test/java/com/androidplot/PlotTest.java
index dc5e5e88..9e4753bf 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,8 +33,8 @@
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.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -47,8 +44,9 @@ public class PlotTest extends AndroidplotTest {
SeriesRegistry mockSeriesRegistry;
@Test
- public void testInit_withoutAttrs() throws Exception {
- Plot plot = mock(Plot.class);
+ public void testInit_withoutAttrs() {
+ //Plot plot = mock(Plot.class);
+ Plot plot = spy(new MockPlot("MockPlot"));
plot.init(RuntimeEnvironment.application, null, 0);
verify(plot, times(1)).onPreInit();
@@ -56,15 +54,17 @@ public void testInit_withoutAttrs() throws Exception {
}
@Test
- public void testInit_withAttrs() throws Exception {
- Plot plot = new MockPlot("MockPlot");
+ public void testInit_withAttrs() {
+ Plot plot = spy(new MockPlot("MockPlot"));
AttributeSet attrs = mock(AttributeSet.class);
plot.init(RuntimeEnvironment.application, attrs, 0);
- // TODO: verifications
+
+ verify(plot, times(1)).onPreInit();
+ verify(plot, times(1)).onAfterConfig();
}
@Test
- public void testAddSeries() throws Exception {
+ public void testAddSeries() {
Plot plot = new MockPlot("MockPlot");
MockSeries m1 = new MockSeries();
@@ -97,7 +97,7 @@ public void testAddSeries() throws Exception {
}
@Test
- public void testRemoveSeries() throws Exception {
+ public void testRemoveSeries() {
Plot plot = new MockPlot("MockPlot");
@@ -157,7 +157,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 +185,7 @@ public void testGetFormatter() throws Exception {
}
@Test
- public void testGetRendererList() throws Exception {
+ public void testGetRendererList() {
Plot plot = new MockPlot("MockPlot");
@@ -206,7 +206,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 +232,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 +379,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 +396,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/pie/PieRendererTest.java b/androidplot-core/src/test/java/com/androidplot/pie/PieRendererTest.java
index dba4ce43..e504582a 100644
--- a/androidplot-core/src/test/java/com/androidplot/pie/PieRendererTest.java
+++ b/androidplot-core/src/test/java/com/androidplot/pie/PieRendererTest.java
@@ -110,7 +110,7 @@ public void testOnRender() throws Exception {
}
@Test
- public void testGetContainingSegment() throws Exception {
+ public void getContainingSegment_returnsCorrectSegment() throws Exception {
Segment segment1 = spy(new Segment("s1", 25));
Segment segment2 = spy(new Segment("s2", 25));
Segment segment3 = spy(new Segment("s3", 25));
@@ -150,6 +150,45 @@ public void testGetContainingSegment() throws Exception {
assertEquals(segment1, renderer.getContainingSegment(new PointF(100, 0)));
}
+ @Test
+ public void getContainingSegment_handlesSegmentsLargerThanHalfPie() throws Exception {
+ Segment segment1 = spy(new Segment("s1", 25));
+ Segment segment2 = spy(new Segment("s2", 24));
+ Segment segment3 = spy(new Segment("s3", 51));
+ SegmentFormatter formatter = spy(
+ new SegmentFormatter(Color.GREEN, Color.GREEN, Color.GREEN, Color.GREEN));
+ PieRenderer renderer = formatter.getRendererInstance(pieChart);
+
+ pieChart.addSegment(segment1, formatter);
+ pieChart.addSegment(segment2, formatter);
+ pieChart.addSegment(segment3, formatter);
+
+ // southeast
+ assertEquals(segment1, renderer.getContainingSegment(new PointF(100, 100)));
+
+ // southwest
+ assertEquals(segment2, renderer.getContainingSegment(new PointF(0, 100)));
+
+ // northwest
+ assertEquals(segment3, renderer.getContainingSegment(new PointF(0, 0)));
+
+ // northeast
+ assertEquals(segment3, renderer.getContainingSegment(new PointF(100, 0)));
+
+ renderer.setStartDegs(90);
+ // southeast
+ assertEquals(segment2, renderer.getContainingSegment(new PointF(100, 100)));
+
+ // southwest
+ assertEquals(segment3, renderer.getContainingSegment(new PointF(0, 100)));
+
+ // northwest
+ assertEquals(segment3, renderer.getContainingSegment(new PointF(0, 0)));
+
+ // northeast
+ assertEquals(segment1, renderer.getContainingSegment(new PointF(100, 0)));
+ }
+
@Test
public void testDegsToScreenDegs() throws Exception {
assertEquals(0f, PieRenderer.degsToScreenDegs(0));
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/RenderStackTest.java b/androidplot-core/src/test/java/com/androidplot/ui/RenderStackTest.java
new file mode 100644
index 00000000..59bab380
--- /dev/null
+++ b/androidplot-core/src/test/java/com/androidplot/ui/RenderStackTest.java
@@ -0,0 +1,52 @@
+package com.androidplot.ui;
+
+import com.androidplot.Plot;
+import com.androidplot.SeriesRegistry;
+import com.androidplot.test.AndroidplotTest;
+import com.androidplot.xy.LineAndPointFormatter;
+import com.androidplot.xy.LineAndPointRenderer;
+import com.androidplot.xy.XYSeries;
+import com.google.common.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class RenderStackTest extends AndroidplotTest {
+
+ @Mock Plot plot;
+ @Mock SeriesRegistry seriesRegistry;
+
+ @Before
+ public void before() {
+ when(plot.getRegistry()).thenReturn(seriesRegistry);
+ }
+
+ @Test
+ public void disable_disablesElements_ofSpecifiedRendererOnly() {
+ final SeriesBundle s1 = new SeriesBundle(mock(XYSeries.class),
+ new LineAndPointFormatter());
+ final SeriesBundle s2 = new SeriesBundle(mock(XYSeries.class),
+ new LineAndPointFormatter());
+ when(seriesRegistry.getSeriesAndFormatterList()).thenReturn(Lists.newArrayList(s1, s2));
+ RenderStack renderStack = new RenderStack<>(plot);
+
+ renderStack.sync();
+ assertEquals(2, renderStack.getElements().size());
+ for(RenderStack.StackElement element : renderStack.getElements()) {
+ assertTrue(element.isEnabled());
+ }
+
+ renderStack.disable(LineAndPointRenderer.class);
+ assertEquals(2, renderStack.getElements().size());
+ for(RenderStack.StackElement element : renderStack.getElements()) {
+ assertFalse(element.isEnabled());
+ }
+ }
+}
diff --git a/androidplot-core/src/test/java/com/androidplot/ui/widget/TextLabelWidgetTest.java b/androidplot-core/src/test/java/com/androidplot/ui/widget/TextLabelWidgetTest.java
index 6ed81b7b..bdb48f92 100644
--- a/androidplot-core/src/test/java/com/androidplot/ui/widget/TextLabelWidgetTest.java
+++ b/androidplot-core/src/test/java/com/androidplot/ui/widget/TextLabelWidgetTest.java
@@ -14,9 +14,9 @@
import org.mockito.InOrder;
import org.mockito.Mock;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyFloat;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
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
new file mode 100644
index 00000000..cae6c056
--- /dev/null
+++ b/androidplot-core/src/test/java/com/androidplot/ui/widget/WidgetTest.java
@@ -0,0 +1,113 @@
+package com.androidplot.ui.widget;
+
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import androidx.annotation.NonNull;
+
+import com.androidplot.test.AndroidplotTest;
+import com.androidplot.ui.Anchor;
+import com.androidplot.ui.HorizontalPositioning;
+import com.androidplot.ui.LayoutManager;
+import com.androidplot.ui.PositionMetrics;
+import com.androidplot.ui.Size;
+import com.androidplot.ui.SizeMetric;
+import com.androidplot.ui.SizeMode;
+import com.androidplot.ui.VerticalPositioning;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import static junit.framework.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class WidgetTest extends AndroidplotTest {
+
+ @Mock
+ LayoutManager layoutManager;
+
+ @Mock
+ Size size;
+
+ @Mock
+ Canvas canvas;
+
+ Widget widget;
+
+ PositionMetrics positionMetrics;
+
+ @Before
+ public void before() {
+
+ positionMetrics = new PositionMetrics(
+ 0, HorizontalPositioning.ABSOLUTE_FROM_LEFT,
+ 0, VerticalPositioning.ABSOLUTE_FROM_TOP, Anchor.LEFT_TOP);
+ when(size.getHeight()).thenReturn(new SizeMetric(100, SizeMode.ABSOLUTE));
+ when(size.getWidth()).thenReturn(new SizeMetric(100, SizeMode.ABSOLUTE));
+ widget = spy(new TestWidget(layoutManager, size));
+ widget.setPositionMetrics(positionMetrics);
+ widget.refreshLayout();
+ }
+
+ @Test
+ public void setGetAnchor_setsAndGetsAnchor() {
+ widget.setAnchor(Anchor.LEFT_TOP);
+ assertEquals(Anchor.LEFT_TOP, widget.getAnchor());
+
+ widget.setAnchor(Anchor.RIGHT_BOTTOM);
+ assertEquals(Anchor.RIGHT_BOTTOM, widget.getAnchor());
+ }
+
+ @Test
+ public void position_withoutAnchor_updatesPositionWithExistingAnchor() {
+ final Anchor existingAnchor = widget.getAnchor();
+ widget.position(1, HorizontalPositioning.ABSOLUTE_FROM_RIGHT,
+ 1, VerticalPositioning.ABSOLUTE_FROM_BOTTOM);
+
+ assertEquals(existingAnchor, widget.getAnchor());
+ assertEquals(1F, widget.getPositionMetrics().getYPositionMetric().getValue());
+ assertEquals(1F, widget.getPositionMetrics().getXPositionMetric().getValue());
+ }
+
+ @Test
+ public void position_withAnchor_updatesPositionWithNewAnchor() {
+ final Anchor newAnchor = Anchor.CENTER;
+ widget.position(1, HorizontalPositioning.ABSOLUTE_FROM_RIGHT,
+ 1, VerticalPositioning.ABSOLUTE_FROM_BOTTOM, newAnchor);
+
+ assertEquals(newAnchor, widget.getAnchor());
+ assertEquals(1F, widget.getPositionMetrics().getYPositionMetric().getValue());
+ assertEquals(1F, widget.getPositionMetrics().getXPositionMetric().getValue());
+ }
+
+ @Test
+ public void draw_sizeChanged_invokesOnResizeBeforeDoOnDraw() {
+ InOrder inOrder = Mockito.inOrder(widget);
+
+ widget.draw(canvas);
+
+ inOrder.verify(widget).onResize(isNull(), any(RectF.class));
+ inOrder.verify(widget).doOnDraw(eq(canvas), any(RectF.class));
+ verify(widget).onResize(isNull(), any(RectF.class));
+ }
+
+ static class TestWidget extends Widget {
+
+ public TestWidget(@NonNull LayoutManager layoutManager, @NonNull Size size) {
+ super(layoutManager, size);
+ }
+
+ @Override
+ protected void doOnDraw(Canvas canvas, RectF widgetRect) {
+ // nothing to do
+ }
+ }
+}
diff --git a/androidplot-core/src/test/java/com/androidplot/util/FastNumberTest.java b/androidplot-core/src/test/java/com/androidplot/util/FastNumberTest.java
new file mode 100644
index 00000000..8bca2a38
--- /dev/null
+++ b/androidplot-core/src/test/java/com/androidplot/util/FastNumberTest.java
@@ -0,0 +1,136 @@
+package com.androidplot.util;
+
+import android.annotation.SuppressLint;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+@SuppressWarnings({"UnnecessaryBoxing", "ObjectEqualsNull", "EqualsBetweenInconvertibleTypes", "EqualsWithItself", "NumberEquality"})
+@SuppressLint("UseValueOf")
+public class FastNumberTest {
+
+ private static final Number[] NUMBERS = new Number[]{
+ new Integer(0),
+ new Integer(1),
+ new Integer(-1),
+ new Integer(Integer.MAX_VALUE),
+ new Integer(Integer.MIN_VALUE),
+
+ new Long(0),
+ new Long(1),
+ new Long(-1),
+ new Long(Long.MAX_VALUE),
+ new Long(Long.MIN_VALUE),
+
+ new Double(0),
+ new Double(1),
+ new Double(-1),
+ new Double(87654321.12345d),
+ new Double(0.0002f),
+ new Double(Double.MAX_VALUE),
+ new Double(Double.MIN_VALUE),
+
+ new Float(0),
+ new Float(1.0f),
+ new Float(-1.0f),
+ new Float(8765.4321f),
+ new Float(1098.576f),
+ new Float(Float.MIN_VALUE),
+ new Float(Float.MAX_VALUE),
+ };
+
+ private static final Number[] NUMBERS_CLONE = new Number[]{
+ new Integer(0),
+ new Integer(1),
+ new Integer(-1),
+ new Integer(Integer.MAX_VALUE),
+ new Integer(Integer.MIN_VALUE),
+
+ new Long(0),
+ new Long(1),
+ new Long(-1),
+ new Long(Long.MAX_VALUE),
+ new Long(Long.MIN_VALUE),
+
+ new Double(0),
+ new Double(1),
+ new Double(-1),
+ new Double(87654321.12345d),
+ new Double(0.0002f),
+ new Double(Double.MAX_VALUE),
+ new Double(Double.MIN_VALUE),
+
+ new Float(0),
+ new Float(1.0f),
+ new Float(-1.0f),
+ new Float(8765.4321f),
+ new Float(1098.576f),
+ new Float(Float.MIN_VALUE),
+ new Float(Float.MAX_VALUE),
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ }
+
+ @After
+ public void tearDown() throws Exception {
+
+ }
+
+ @Test
+ public void orNull_returnsNull_ifNullNumber() {
+ //noinspection ConstantConditions
+ assertNull(FastNumber.orNull(null));
+ }
+
+ @Test
+ public void equals_returnsTrue_ifFromSameNumberInstance() {
+ for (Number number : NUMBERS) {
+ assertTrue("Equality test failed on " + number, FastNumber.orNull(number).equals(FastNumber.orNull(number)));
+ }
+ }
+
+ @Test
+ public void equals_returnsTrue_ifFromSameNumber() {
+ assertEquals("misconfigured test values", NUMBERS.length, NUMBERS_CLONE.length);
+ for (int i = 0; i < NUMBERS.length; i++) {
+ assertEquals("misconfigured test values", NUMBERS[i], NUMBERS_CLONE[i]);
+ assertTrue(FastNumber.orNull(NUMBERS[i]).equals(FastNumber.orNull(NUMBERS_CLONE[i])));
+ }
+ }
+
+ @Test
+ public void equals_returnsFalse_ifNumberIsDifferent() {
+ for (int i = 0; i < NUMBERS.length; i++) {
+ for (int j = 0; j < NUMBERS.length; j++) {
+ if (j == i) {
+ continue;
+ }
+ assertNotEquals("duplicate test values at index " + i + " and " + j, NUMBERS[i], NUMBERS[j]);
+ assertFalse(FastNumber.orNull(NUMBERS[i]).equals(FastNumber.orNull(NUMBERS[j])));
+ }
+ }
+ }
+
+ @Test
+ public void hashCode_isEqual_ifInstanceIsEqual() {
+ for (Number number : NUMBERS) {
+ for (Number number2 : NUMBERS) {
+ FastNumber fastNumber1 = FastNumber.orNull(number);
+ FastNumber fastNumber2 = FastNumber.orNull(number2);
+ if (fastNumber1.equals(fastNumber2)) {
+ assertEquals(fastNumber1.hashCode(), fastNumber2.hashCode());
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/androidplot-core/src/test/java/com/androidplot/util/PlotStatisticsTest.java b/androidplot-core/src/test/java/com/androidplot/util/PlotStatisticsTest.java
index b063235a..4454c69c 100644
--- a/androidplot-core/src/test/java/com/androidplot/util/PlotStatisticsTest.java
+++ b/androidplot-core/src/test/java/com/androidplot/util/PlotStatisticsTest.java
@@ -27,9 +27,9 @@
import org.mockito.InjectMocks;
import org.mockito.Mock;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyFloat;
-import static org.mockito.Matchers.anyString;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
diff --git a/androidplot-core/src/test/java/com/androidplot/util/SeriesUtilsTest.java b/androidplot-core/src/test/java/com/androidplot/util/SeriesUtilsTest.java
index 4ff129bc..df9014cd 100644
--- a/androidplot-core/src/test/java/com/androidplot/util/SeriesUtilsTest.java
+++ b/androidplot-core/src/test/java/com/androidplot/util/SeriesUtilsTest.java
@@ -51,7 +51,7 @@ public void tearDown() throws Exception {
}
@Test
- public void testSeriesMinMax() {
+ public void minMax_onSimpleXYSeries_calculatesExpectedRegion() {
SimpleXYSeries series = new SimpleXYSeries(LINEAR, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, null);
RectRegion minMax = SeriesUtils.minMax(series);
assertEquals(0, minMax.getMinX().doubleValue(), 0);
@@ -103,14 +103,68 @@ public void testSeriesMinMax() {
}
@Test
- public void minMax_usesSeriesMinMax_onFastXYSeries() {
+ public void minMax_onFastXYSeries_usesSeriesMinMax() {
FastXYSeries series = mock(FastXYSeries.class);
SeriesUtils.minMax(series);
verify(series).minMax();
}
@Test
- public void testListMinMax() {
+ public void minMax_calculatesExpectedRegion_onFastXYSeriesWithLayoutConstraints() {
+ FastXYSeries series = new FastXYSeries() {
+
+ // create a couple arrays of y-values to plot:
+ final ArrayList times = new ArrayList<>();
+ final ArrayList values = new ArrayList<>();
+
+ {
+ for (int i = 0; i < 10; i++) {
+ times.add(i);
+ values.add(i);
+ }
+ }
+
+ @Override
+ public int size() {
+ return times.size();
+ }
+
+ @Override
+ public Number getX(int index) {
+ return times.get(index);
+ }
+
+ @Override
+ public Number getY(int index) {
+ return values.get(index);
+ }
+
+ @Override
+ public String getTitle() {
+ return "Isaac's crazy thing";
+ }
+
+ @Override
+ public RectRegion minMax() {
+ return new RectRegion(0, 10, 0, 10);
+ }
+ };
+
+ final XYConstraints constraints = new XYConstraints();
+ constraints.setDomainLowerBoundaryMode(BoundaryMode.FIXED);
+ constraints.setDomainUpperBoundaryMode(BoundaryMode.FIXED);
+ constraints.setMinX(5);
+ constraints.setMaxX(9);
+
+ final RectRegion result = SeriesUtils.minMax(constraints, series);
+ assertEquals(5d, result.getMinX().doubleValue());
+ assertEquals(9d, result.getMaxX().doubleValue());
+ assertEquals(5d, result.getMinY().doubleValue());
+ assertEquals(9d, result.getMaxY().doubleValue());
+ }
+
+ @Test
+ public void minMax_onSeriesList_producesAggregateResult() {
Region minMax = SeriesUtils.minMax(LINEAR);
assertEquals(1, minMax.getMin().doubleValue(), 0);
assertEquals(8, minMax.getMax().doubleValue(), 0);
@@ -141,7 +195,7 @@ public void testListMinMax() {
}
@Test
- public void testGetNullRegion() {
+ public void getNullRegion_producesExpectedResult() {
XYSeries s1 = new SimpleXYSeries(
SimpleXYSeries.ArrayFormat.XY_VALS_INTERLEAVED, "s1",
0, 0, // 0
@@ -176,7 +230,7 @@ public void testGetNullRegion() {
}
@Test
- public void testIboundsMin() {
+ public void iBoundsMin_findsMin() {
XYSeries s1 = new SimpleXYSeries(
Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12),
Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12),
@@ -203,7 +257,7 @@ public void testIboundsMin() {
}
@Test
- public void testIboundsMax() {
+ public void iBoundsMax_findsMax() {
XYSeries s1 = new SimpleXYSeries(
Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12),
Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12),
@@ -230,7 +284,7 @@ public void testIboundsMax() {
}
@Test
- public void testIbounds() {
+ public void iBounds_findsMinMax() {
FastXYSeries series = mock(FastXYSeries.class);
when(series.size()).thenReturn(3);
when(series.getX(0)).thenReturn(0);
diff --git a/androidplot-core/src/test/java/com/androidplot/xy/AdvanceLineAndPointRendererTest.java b/androidplot-core/src/test/java/com/androidplot/xy/AdvanceLineAndPointRendererTest.java
index 647882c5..b1600828 100644
--- a/androidplot-core/src/test/java/com/androidplot/xy/AdvanceLineAndPointRendererTest.java
+++ b/androidplot-core/src/test/java/com/androidplot/xy/AdvanceLineAndPointRendererTest.java
@@ -24,7 +24,6 @@
import org.junit.*;
import org.mockito.*;
-import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
/**
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..08cc1480 100644
--- a/androidplot-core/src/test/java/com/androidplot/xy/BarRendererTest.java
+++ b/androidplot-core/src/test/java/com/androidplot/xy/BarRendererTest.java
@@ -33,9 +33,9 @@
import java.util.List;
import static junit.framework.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyFloat;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -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/BubbleRendererTest.java b/androidplot-core/src/test/java/com/androidplot/xy/BubbleRendererTest.java
index cd0ff2b1..94a0be50 100644
--- a/androidplot-core/src/test/java/com/androidplot/xy/BubbleRendererTest.java
+++ b/androidplot-core/src/test/java/com/androidplot/xy/BubbleRendererTest.java
@@ -24,7 +24,6 @@
import org.junit.*;
import org.mockito.*;
-import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
/**
diff --git a/androidplot-core/src/test/java/com/androidplot/xy/CandlestickRendererTest.java b/androidplot-core/src/test/java/com/androidplot/xy/CandlestickRendererTest.java
index a7fba775..8eda23b9 100644
--- a/androidplot-core/src/test/java/com/androidplot/xy/CandlestickRendererTest.java
+++ b/androidplot-core/src/test/java/com/androidplot/xy/CandlestickRendererTest.java
@@ -24,7 +24,6 @@
import org.junit.*;
import org.mockito.*;
-import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
public class CandlestickRendererTest extends AndroidplotTest {
diff --git a/androidplot-core/src/test/java/com/androidplot/xy/CatmullRomInterpolatorTest.java b/androidplot-core/src/test/java/com/androidplot/xy/CatmullRomInterpolatorTest.java
new file mode 100644
index 00000000..1d672daf
--- /dev/null
+++ b/androidplot-core/src/test/java/com/androidplot/xy/CatmullRomInterpolatorTest.java
@@ -0,0 +1,59 @@
+package com.androidplot.xy;
+
+import org.junit.Test;
+
+import java.util.List;
+
+import static junit.framework.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+public class CatmullRomInterpolatorTest {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void interpolate_invalidPointsPerSegment_throwsIllegalArgumentException() {
+ final XYSeries series = mock(XYSeries.class);
+ final CatmullRomInterpolator.Params params =
+ new CatmullRomInterpolator.Params(1, CatmullRomInterpolator.Type.Centripetal);
+
+ new CatmullRomInterpolator().interpolate(series, params);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void interpolate_twoElementSeries_throwsIllegalArgumentException() {
+ final XYSeries series = new SimpleXYSeries(SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "test", 1, 2);
+ final CatmullRomInterpolator.Params params =
+ new CatmullRomInterpolator.Params(2, CatmullRomInterpolator.Type.Centripetal);
+
+ new CatmullRomInterpolator().interpolate(series, params);
+ }
+
+ @Test
+ public void interpolate_threeElementSeriesAndThreePointsPerSegment_producesFivePoints() {
+ final XYSeries series = new SimpleXYSeries(SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "test", 1, 2, 3);
+ final CatmullRomInterpolator.Params params =
+ new CatmullRomInterpolator.Params(3, CatmullRomInterpolator.Type.Centripetal);
+ final List interpolated = new CatmullRomInterpolator().interpolate(series, params);
+
+ assertEquals(5, interpolated.size());
+
+ // control points should exactly match input:
+ assertEquals(1, interpolated.get(0).y);
+ assertEquals(2, interpolated.get(2).y);
+ assertEquals(3, interpolated.get(4).y);
+ }
+
+ @Test
+ public void interpolate_threeElementSeriesAndFourPointsPerSegment_producesSavenPoints() {
+ final XYSeries series = new SimpleXYSeries(SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "test", 1, 2, 3);
+ final CatmullRomInterpolator.Params params =
+ new CatmullRomInterpolator.Params(4, CatmullRomInterpolator.Type.Centripetal);
+ final List interpolated = new CatmullRomInterpolator().interpolate(series, params);
+
+ assertEquals(7, interpolated.size());
+
+ // control points should exactly match input:
+ assertEquals(1, interpolated.get(0).y);
+ assertEquals(2, interpolated.get(3).y);
+ assertEquals(3, interpolated.get(6).y);
+ }
+}
diff --git a/androidplot-core/src/test/java/com/androidplot/xy/FastLineAndPointRendererTest.java b/androidplot-core/src/test/java/com/androidplot/xy/FastLineAndPointRendererTest.java
index de81e70c..cf083d9e 100644
--- a/androidplot-core/src/test/java/com/androidplot/xy/FastLineAndPointRendererTest.java
+++ b/androidplot-core/src/test/java/com/androidplot/xy/FastLineAndPointRendererTest.java
@@ -24,8 +24,8 @@
import org.junit.runner.*;
import org.mockito.*;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
diff --git a/androidplot-core/src/test/java/com/androidplot/xy/LTTBSamplerTest.java b/androidplot-core/src/test/java/com/androidplot/xy/LTTBSamplerTest.java
index 3269082d..800992f9 100644
--- a/androidplot-core/src/test/java/com/androidplot/xy/LTTBSamplerTest.java
+++ b/androidplot-core/src/test/java/com/androidplot/xy/LTTBSamplerTest.java
@@ -27,7 +27,6 @@
import java.util.*;
import static junit.framework.Assert.assertEquals;
-import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
public class LTTBSamplerTest extends AndroidplotTest {
diff --git a/androidplot-core/src/test/java/com/androidplot/xy/LineAndPointRendererTest.java b/androidplot-core/src/test/java/com/androidplot/xy/LineAndPointRendererTest.java
index 9cb03cd1..44a7b22d 100644
--- a/androidplot-core/src/test/java/com/androidplot/xy/LineAndPointRendererTest.java
+++ b/androidplot-core/src/test/java/com/androidplot/xy/LineAndPointRendererTest.java
@@ -26,7 +26,6 @@
import java.util.*;
import static junit.framework.Assert.assertEquals;
-import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
public class LineAndPointRendererTest extends AndroidplotTest {
diff --git a/androidplot-core/src/test/java/com/androidplot/xy/SimpleXYSeriesTest.java b/androidplot-core/src/test/java/com/androidplot/xy/SimpleXYSeriesTest.java
index 50fd0f8a..dc21ab7c 100644
--- a/androidplot-core/src/test/java/com/androidplot/xy/SimpleXYSeriesTest.java
+++ b/androidplot-core/src/test/java/com/androidplot/xy/SimpleXYSeriesTest.java
@@ -26,7 +26,7 @@
public class SimpleXYSeriesTest {
@Test
- public void testYValsOnlyConstructor() throws Exception {
+ public void constructor_yValsOnly() throws Exception {
Number[] yVals = {5, 6, 7, 8, 9};
SimpleXYSeries series = new SimpleXYSeries(Arrays.asList(yVals), SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "test");
@@ -44,7 +44,7 @@ public void testYValsOnlyConstructor() throws Exception {
}
@Test
- public void testXYInterleavedConstructor() throws Exception {
+ public void constructor_xyInterleaved() throws Exception {
Number[] yVals = {55, 5, 66, 6, 77, 7, 88, 8, 99, 9};
SimpleXYSeries series = new SimpleXYSeries(Arrays.asList(yVals), SimpleXYSeries.ArrayFormat.XY_VALS_INTERLEAVED, "test");
@@ -62,7 +62,7 @@ public void testXYInterleavedConstructor() throws Exception {
}
@Test
- public void testTwoListConstructor() throws Exception {
+ public void constructor_xAndYLists() throws Exception {
Number[] yVals = {5, 6, 7, 8, 9};
Number[] xVals = {1, 2, 3, 4, 5};
SimpleXYSeries series = new SimpleXYSeries(Arrays.asList(xVals), Arrays.asList(yVals), "test");
@@ -81,7 +81,7 @@ public void testTwoListConstructor() throws Exception {
}
@Test
- public void testPushPopStuff() throws Exception {
+ public void addRemove_modifiesSeries() throws Exception {
Number[] yVals = {5, 6, 7, 8, 9};
Number[] xVals = {1, 2, 3, 4, 5};
SimpleXYSeries series = new SimpleXYSeries(Arrays.asList(xVals), Arrays.asList(yVals), "test");
@@ -108,7 +108,7 @@ public void testPushPopStuff() throws Exception {
}
@Test
- public void testSet() throws Exception {
+ public void set_setsExpectedValue() throws Exception {
Number[] yVals = {5, 6, 7, 8, 9};
Number[] xVals = {1, 2, 3, 4, 5};
SimpleXYSeries series = new SimpleXYSeries(Arrays.asList(xVals), Arrays.asList(yVals), "test");
@@ -129,7 +129,7 @@ public void testSet() throws Exception {
}
@Test
- public void testResize() throws Exception {
+ public void resize_emptySeries_resizesSeries() {
SimpleXYSeries series = new SimpleXYSeries("series");
series.resize(10);
assertEquals(10, series.size());
@@ -148,7 +148,15 @@ public void testResize() throws Exception {
}
@Test
- public void setXY_setsXAndY() {
+ public void resize_yValsOnly_resizesSeries() {
+ SimpleXYSeries series = new SimpleXYSeries(
+ SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "", 1, 2, 3);
+ series.resize(10);
+ assertEquals(10, series.size());
+ }
+
+ @Test
+ public void setXY_modifiesSeries() {
SimpleXYSeries series = new SimpleXYSeries("series");
series.resize(5);
series.setXY(100, 200, 0);
@@ -158,6 +166,14 @@ public void setXY_setsXAndY() {
}
+ @Test
+ public void setX_yValsOnly_changesValue() {
+ SimpleXYSeries series = new SimpleXYSeries(
+ SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "", 1, 2, 3);
+ series.setX(9, 0);
+ assertEquals(9, series.getX(0));
+ }
+
@Test(expected = NoSuchElementException.class)
public void removeFirst_throwsNoSuchElementException_ifEmpty() {
new SimpleXYSeries("series").removeFirst();
diff --git a/androidplot-core/src/test/java/com/androidplot/xy/XYGraphWidgetTest.java b/androidplot-core/src/test/java/com/androidplot/xy/XYGraphWidgetTest.java
index 4b9f156a..a9182501 100644
--- a/androidplot-core/src/test/java/com/androidplot/xy/XYGraphWidgetTest.java
+++ b/androidplot-core/src/test/java/com/androidplot/xy/XYGraphWidgetTest.java
@@ -28,11 +28,11 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyFloat;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyDouble;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
@@ -67,7 +67,7 @@ public class XYGraphWidgetTest extends AndroidplotTest {
@Before
- public void setUp() throws Exception {
+ public void setUp() {
size = spy(new Size(100, SizeMode.ABSOLUTE, 100, SizeMode.ABSOLUTE));
xyPlot = spy(new XYPlot(getContext(), "XYPlot"));
when(xyPlot.getRegistry()).thenReturn(seriesRegistry);
@@ -79,17 +79,12 @@ public void setUp() throws Exception {
xyPlot.setRangeStep(StepMode.INCREMENT_BY_VAL, 1);
graphWidget = spy(new XYGraphWidget(layoutManager, xyPlot, size));
- graphWidget.setGridRect(new RectF(0, 0, 100, 100));
+ graphWidget.setGridRect(new RectF(0, 0, 10, 100));
graphWidget.setLabelRect(new RectF(0, 0, 100, 100));
}
- @After
- public void tearDown() throws Exception {
-
- }
-
@Test
- public void testProcessAttrs() throws Exception {
+ public void processAttrs_withDefaults_disablesGridClipping() {
XYGraphWidget graphWidget = spy(new XYGraphWidget(layoutManager, xyPlot, size));
graphWidget.processAttrs(typedArray);
@@ -97,13 +92,15 @@ public void testProcessAttrs() throws Exception {
}
@Test
- public void testDoOnDraw_drawGridOnTopFalse() throws Exception {
+ public void doOnDraw_drawGridOnTopFalse_drawsGridAfterData() throws Exception {
XYGraphWidget graphWidget = spy(new XYGraphWidget(layoutManager, xyPlot, size));
graphWidget.setDrawGridOnTop(false);
doNothing().when(graphWidget).drawGrid(canvas);
doNothing().when(graphWidget).drawData(canvas);
- graphWidget.doOnDraw(canvas, new RectF(0, 0, 100, 100));
+ final RectF canvasRect = new RectF(0, 0, 100, 100);
+ graphWidget.onResize(null, canvasRect);
+ graphWidget.doOnDraw(canvas, canvasRect);
InOrder io = inOrder(graphWidget);
io.verify(graphWidget).drawGrid(canvas);
@@ -111,13 +108,15 @@ public void testDoOnDraw_drawGridOnTopFalse() throws Exception {
}
@Test
- public void testDoOnDraw_drawGridOnTopTrue() throws Exception {
+ public void doOnDraw_drawGridOnTopTrue_drawsGridBeforeData() throws Exception {
XYGraphWidget graphWidget = spy(new XYGraphWidget(layoutManager, xyPlot, size));
graphWidget.setDrawGridOnTop(true);
doNothing().when(graphWidget).drawGrid(canvas);
doNothing().when(graphWidget).drawData(canvas);
- graphWidget.doOnDraw(canvas, new RectF(0, 0, 100, 100));
+ final RectF canvasRect = new RectF(0, 0, 100, 100);
+ graphWidget.onResize(null, canvasRect);
+ graphWidget.doOnDraw(canvas, canvasRect);
InOrder io = inOrder(graphWidget);
io.verify(graphWidget).drawData(canvas);
@@ -125,7 +124,7 @@ public void testDoOnDraw_drawGridOnTopTrue() throws Exception {
}
@Test
- public void testDrawMarkers() throws Exception {
+ public void drawMarkers_drawsLineForEachMarker() {
xyPlot.addMarker(new XValueMarker(1, "x"));
xyPlot.addMarker(new YValueMarker(-1, "y"));
@@ -136,42 +135,22 @@ public void testDrawMarkers() throws Exception {
.drawLine(anyFloat(), anyFloat(), anyFloat(), anyFloat(), any(Paint.class));
}
- protected void runDrawGridTest() {
- doNothing().when(graphWidget).
- drawDomainLine(any(Canvas.class), anyFloat(), any(Number.class), any(Paint.class), anyBoolean());
-
- doNothing().when(graphWidget).
- drawRangeLine(any(Canvas.class), anyFloat(), any(Number.class), any(Paint.class), anyBoolean());
-
- xyPlot.setRangeBoundaries(0, 100, BoundaryMode.FIXED);
- xyPlot.setDomainBoundaries(0, 100, BoundaryMode.FIXED);
-
- graphWidget.drawGrid(canvas);
-
- // expecting a 100x100 grid to be drawn:
- verify(graphWidget, times(100))
- .drawDomainLine(eq(canvas), anyFloat(), anyFloat(), any(Paint.class), eq(false));
-
- verify(graphWidget, times(100))
- .drawRangeLine(eq(canvas), anyFloat(), anyFloat(), any(Paint.class), eq(false));
- }
-
@Test
- public void testDrawGrid_nullOrigin() throws Exception {
+ public void drawGrid_nullOrigin_drawsGrid() {
when(xyPlot.getDomainOrigin()).thenReturn(null);
when(xyPlot.getRangeOrigin()).thenReturn(null);
runDrawGridTest();
}
@Test
- public void testDrawGrid_zeroOrigin() throws Exception {
+ public void drawGrid_zeroOrigin_drawsGrid() {
when(xyPlot.getDomainOrigin()).thenReturn(0);
when(xyPlot.getRangeOrigin()).thenReturn(0);
runDrawGridTest();
}
@Test
- public void testDrawGrid_centeredOrigin() throws Exception {
+ public void drawGrid_centeredOrigin_drawsGrid() {
// set origin to midpoint so we exercise
// code to draw lines on both sides of the origin:
@@ -181,7 +160,7 @@ public void testDrawGrid_centeredOrigin() throws Exception {
}
@Test
- public void testDrawCursors_ifCursorPaintAndPositionAreSet() throws Exception {
+ public void drawCursors_withCursorPaintAndPosition_drawsCursorLines() {
final Paint domainCursorPaint = new Paint();
graphWidget.setDomainCursorPaint(domainCursorPaint);
@@ -197,7 +176,7 @@ public void testDrawCursors_ifCursorPaintAndPositionAreSet() throws Exception {
}
@Test
- public void testDrawCursors_ifCursorPaintAndPositionAreNotSet() throws Exception {
+ public void testDrawCursors_noCursorPaintOrPosition_drawsNoCursorLines() {
graphWidget.setDomainCursorPaint(null);
graphWidget.setRangeCursorPaint(null);
@@ -208,7 +187,7 @@ public void testDrawCursors_ifCursorPaintAndPositionAreNotSet() throws Exception
}
@Test
- public void testDrawCursorLabel() throws Exception {
+ public void drawCursorLabel_drawsText() {
graphWidget.setDomainCursorPosition(0f);
graphWidget.setRangeCursorPosition(0f);
XYGraphWidget.CursorLabelFormatter clf = mock(XYGraphWidget.CursorLabelFormatter.class);
@@ -220,8 +199,7 @@ public void testDrawCursorLabel() throws Exception {
}
@Test
- public void testSetLineLabelEdges() throws Exception {
-
+ public void setLineLabelEdges_setsEdges() {
graphWidget.setLineLabelEdges(XYGraphWidget.Edge.LEFT, XYGraphWidget.Edge.BOTTOM);
assertTrue(graphWidget.isLineLabelEnabled(XYGraphWidget.Edge.LEFT));
assertTrue(graphWidget.isLineLabelEnabled(XYGraphWidget.Edge.BOTTOM));
@@ -234,7 +212,7 @@ public void testSetLineLabelEdges() throws Exception {
}
@Test
- public void testSetLineLabelEdges_bitfield() throws Exception {
+ public void setLineLabelEdges_bitfield_setsEdges() {
graphWidget.setLineLabelEdges(
XYGraphWidget.Edge.TOP.getValue() | XYGraphWidget.Edge.RIGHT.getValue());
@@ -246,24 +224,24 @@ public void testSetLineLabelEdges_bitfield() throws Exception {
}
@Test
- public void testScreenToSeries() throws Exception {
+ public void screenToSeries_returnsSeriesCoords() {
when(xyPlot.getBounds()).thenReturn(new RectRegion(-100, 100, -100, 100));
XYCoords coords = graphWidget.screenToSeries(new PointF(0, 0));
assertEquals(-100, coords.x.intValue());
assertEquals(100, coords.y.intValue());
- coords = graphWidget.screenToSeries(new PointF(100, 100));
+ coords = graphWidget.screenToSeries(new PointF(10, 100));
assertEquals(100, coords.x.intValue());
assertEquals(-100, coords.y.intValue());
- coords = graphWidget.screenToSeries(new PointF(50, 50));
+ coords = graphWidget.screenToSeries(new PointF(5, 50));
assertEquals(0, coords.x.intValue());
assertEquals(0, coords.y.intValue());
}
@Test
- public void testSeriesToScreen() throws Exception {
+ public void seriesToScreen_returnsScreenPoint() {
when(xyPlot.getBounds()).thenReturn(new RectRegion(-100, 100, -100, 100));
PointF point = graphWidget.seriesToScreen(new XYCoords(-100, 100));
@@ -271,25 +249,25 @@ public void testSeriesToScreen() throws Exception {
assertEquals(0f, point.y);
point = graphWidget.seriesToScreen(new XYCoords(100, -100));
- assertEquals(100f, point.x);
+ assertEquals(10f, point.x);
assertEquals(100f, point.y);
point = graphWidget.seriesToScreen(new XYCoords(0, 0));
- assertEquals(50f, point.x);
+ assertEquals(5f, point.x);
assertEquals(50f, point.y);
}
@Test
- public void testScreenToSeriesX() throws Exception {
+ public void screenToSeriesX_returnsSeriesValue() {
when(xyPlot.getBounds()).thenReturn(new RectRegion(-100, 100, -100, 100));
assertEquals(-100, graphWidget.screenToSeriesX(new PointF(0, 0)).intValue());
- assertEquals(100, graphWidget.screenToSeriesX(new PointF(100, 100)).intValue());
- assertEquals(0, graphWidget.screenToSeriesX(new PointF(50, 50)).intValue());
+ assertEquals(100, graphWidget.screenToSeriesX(new PointF(10, 100)).intValue());
+ assertEquals(0, graphWidget.screenToSeriesX(new PointF(5, 50)).intValue());
}
@Test
- public void testScreenToSeriesY() throws Exception {
+ public void screenToSeriesY_returnsSeriesValue() {
when(xyPlot.getBounds()).thenReturn(new RectRegion(-100, 100, -100, 100));
assertEquals(100, graphWidget.screenToSeriesY(new PointF(0, 0)).intValue());
@@ -298,20 +276,54 @@ public void testScreenToSeriesY() throws Exception {
}
@Test
- public void testSeriesToScreenX() throws Exception {
+ public void seriesToScreenX_returnsScreenValue() {
when(xyPlot.getBounds()).thenReturn(new RectRegion(-100, 100, -100, 100));
assertEquals(0f, graphWidget.seriesToScreenX(-100));
- assertEquals(100f, graphWidget.seriesToScreenX(100));
- assertEquals(50f, graphWidget.seriesToScreenX(0));
+ assertEquals(10f, graphWidget.seriesToScreenX(100));
+ assertEquals(5f, graphWidget.seriesToScreenX(0));
}
@Test
- public void testSeriesToScreenY() throws Exception {
+ public void seriesToScreenY_returnsScreenValue() {
when(xyPlot.getBounds()).thenReturn(new RectRegion(-100, 100, -100, 100));
- assertEquals(0f, graphWidget.seriesToScreenY(100));
- assertEquals(100f, graphWidget.seriesToScreenY(-100));
+ assertEquals(100f, graphWidget.seriesToScreenY(100));
+ assertEquals(0f, graphWidget.seriesToScreenY(-100));
assertEquals(50f, graphWidget.seriesToScreenY(0));
}
+
+ @Test
+ public void setGridInsets_updatesGridRect() {
+ graphWidget.setGridInsets(new com.androidplot.ui.Insets(0, 0, 0, 0));
+ final RectF oldRect = graphWidget.getGridRect();
+
+ graphWidget.setGridInsets(new com.androidplot.ui.Insets(2, 2, 2, 2));
+ final RectF newRect = graphWidget.getGridRect();
+
+ assertEquals(oldRect.left + 2, newRect.left);
+ assertEquals(oldRect.top + 2, newRect.top);
+ assertEquals(oldRect.right - 2, newRect.right);
+ assertEquals(oldRect.bottom -2, newRect.bottom);
+ }
+
+ private void runDrawGridTest() {
+ doNothing().when(graphWidget).
+ drawDomainLine(any(Canvas.class), anyFloat(), any(Number.class), any(Paint.class), anyBoolean(), anyBoolean());
+
+ doNothing().when(graphWidget).
+ drawRangeLine(any(Canvas.class), anyFloat(), any(Number.class), any(Paint.class), anyBoolean(), anyBoolean());
+
+ xyPlot.setRangeBoundaries(0, 100, BoundaryMode.FIXED);
+ xyPlot.setDomainBoundaries(0, 100, BoundaryMode.FIXED);
+
+ graphWidget.drawGrid(canvas);
+
+ // expecting a 100x100 grid to be drawn:
+ verify(graphWidget, times(100))
+ .drawDomainLine(eq(canvas), anyFloat(), anyDouble(), any(Paint.class), eq(false), anyBoolean());
+
+ verify(graphWidget, times(100))
+ .drawRangeLine(eq(canvas), anyFloat(), anyDouble(), any(Paint.class), eq(false), anyBoolean());
+ }
}
diff --git a/androidplot-core/src/test/java/com/androidplot/xy/XYLegendWidgetTest.java b/androidplot-core/src/test/java/com/androidplot/xy/XYLegendWidgetTest.java
index bbb2bec5..778fb40a 100644
--- a/androidplot-core/src/test/java/com/androidplot/xy/XYLegendWidgetTest.java
+++ b/androidplot-core/src/test/java/com/androidplot/xy/XYLegendWidgetTest.java
@@ -17,61 +17,100 @@
package com.androidplot.xy;
import android.graphics.*;
-import com.androidplot.Plot;
import com.androidplot.test.AndroidplotTest;
-import org.junit.After;
+import com.androidplot.ui.DynamicTableModel;
+import com.androidplot.ui.LayoutManager;
+import com.androidplot.ui.Size;
+import com.androidplot.ui.SizeMode;
+import com.google.common.collect.Lists;
+
+import org.junit.Before;
import org.junit.Test;
-import org.robolectric.RuntimeEnvironment;
-import java.util.Arrays;
-import static junit.framework.Assert.assertEquals;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import java.util.List;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
public class XYLegendWidgetTest extends AndroidplotTest {
- static class MockXYPlot extends XYPlot {
+ @Mock LayoutManager layoutManager;
+ @Mock XYPlot xyPlot;
+ @Mock Canvas canvas;
+ @Mock XYRegionFormatter xyRegionFormatter;
+ LineAndPointRenderer lineAndPointRenderer;
- public MockXYPlot() {
- super(RuntimeEnvironment.application, "Test",
- Plot.RenderMode.USE_MAIN_THREAD);
- }
+ Size widgetSize = new Size(100, SizeMode.ABSOLUTE, 100, SizeMode.ABSOLUTE);
+ Size iconSize = new Size(10, SizeMode.ABSOLUTE, 10, SizeMode.ABSOLUTE);
+ XYSeriesRegistry seriesRegistry;
- public void exposedOnSizeChanged(int w, int h, int oldw, int oldh) {
- this.onSizeChanged(w, h, oldw, oldh);
- }
+ XYLegendWidget legendWidget;
- public void exposedOnDraw(Canvas canvas) {
- this.onDraw(canvas);
- }
- }
+ @Before
+ public void before() {
+ seriesRegistry = new XYSeriesRegistry();
+ legendWidget = spy(new XYLegendWidget(layoutManager, xyPlot, widgetSize,
+ new DynamicTableModel(4, 4), iconSize));
- @After
- public void tearDown() throws Exception {}
+ lineAndPointRenderer = new LineAndPointRenderer(xyPlot);
- @Test
- public void testDoOnDraw() throws Exception {
- MockXYPlot plot = new MockXYPlot();
+ when(xyPlot.getRegistry()).thenReturn(seriesRegistry);
+ when(xyPlot.getRendererList()).thenReturn(Lists.newArrayList(lineAndPointRenderer));
+ when(xyPlot.getRenderer(any(Class.class))).thenReturn(lineAndPointRenderer);
+ }
- SimpleXYSeries s1 = new SimpleXYSeries((Arrays.asList(1, 2, 3)),
- SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "s1");
+ @Test
+ public void draw_drawsLegendIcons_forEnabledItemsOnly() throws Exception {
+ final XYSeries s1 = mock(XYSeries.class);
+ final XYSeriesFormatter f1 = new LineAndPointFormatter();
+ f1.setLegendIconEnabled(true);
+
+ final XYSeries s2 = mock(XYSeries.class);
+ final XYSeriesFormatter f2 = new LineAndPointFormatter();
+ f2.setLegendIconEnabled(false);
+
+ final RectRegion r1 = new RectRegion(0, 0, 10, 10, "r1");
+ final RectRegion r2 = new RectRegion(0, 0, 20, 20, "r2");
+ f1.addRegion(r1, new XYRegionFormatter(0));
+ f2.addRegion(r2, new XYRegionFormatter(0));
+
+ seriesRegistry.add(s1, f1);
+ seriesRegistry.add(s2, f2);
+ legendWidget.draw(canvas);
+
+ verify(legendWidget, times(2))
+ .drawRegionLegendIcon(any(Canvas.class), any(RectF.class), any(XYRegionFormatter.class));
+ verify(legendWidget, times(3))
+ .drawIcon(any(Canvas.class), any(RectF.class), any(XYLegendItem.class));
+ }
- plot.addSeries(s1, new LineAndPointFormatter(
- Color.RED, Color.GREEN, Color.BLUE, null));
+ @Test
+ public void draw_sortsItemsAlphabeticallyByTitle() throws Exception{
+ final XYLegendItem i1 = new XYLegendItem(XYLegendItem.Type.SERIES,
+ new LineAndPointFormatter(), "zoo");
+ final XYLegendItem i2 = new XYLegendItem(XYLegendItem.Type.SERIES,
+ new LineAndPointFormatter(), "apple");
+ final XYLegendItem i3 = new XYLegendItem(XYLegendItem.Type.SERIES,
+ new LineAndPointFormatter(), "boo");
- assertEquals(1, plot.getRegistry().size());
+ final List legendItems = Lists.newArrayList(i1, i2, i3);
+ doReturn(legendItems).when(legendWidget).getLegendItems();
- plot.exposedOnSizeChanged(100, 100, 100, 100);
- plot.redraw();
- // have to manually invoke this because the invalidate()
- // invoked by redraw() is a stub and will not result in onDraw being called.
- plot.exposedOnDraw(new Canvas());
+ legendWidget.draw(canvas);
- plot.removeSeries(s1);
- assertEquals(0, plot.getRegistry().size());
- plot.addSeries(s1, new BarFormatter(Color.RED, Color.GREEN));
- plot.redraw();
+ InOrder inOrder = Mockito.inOrder(legendWidget);
- // throws NullPointerException before fix
- // for ANDROIDPLOT-166 was applied.
- plot.exposedOnDraw(new Canvas());
+ inOrder.verify(legendWidget).drawIcon(any(Canvas.class), any(RectF.class), eq(i2));
+ inOrder.verify(legendWidget).drawIcon(any(Canvas.class), any(RectF.class), eq(i3));
+ inOrder.verify(legendWidget).drawIcon(any(Canvas.class), any(RectF.class), eq(i1));
}
-
}
diff --git a/build.gradle b/build.gradle
index 973ff339..4b5e18de 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,44 +14,32 @@
* limitations under the License.
*/
-apply plugin: 'java'
-
-allprojects {
- repositories {
- jcenter()
- }
-}
-
ext {
- theBuildToolsVersion = '25.0.2'
- theCompileSdkVersion = 25
- theTargetSdkVersion = 25
+ theCompileSdkVersion = 34
+ theTargetSdkVersion = 34
theMinSdkVersion = 5
- theVersionName = '1.4.3'
- theVersionCode = 0
+ theVersionName = '1.5.11'
+ theVersionCode = 227
+ gitUrl = 'https://github.com/halfhp/androidplot.git'
}
buildscript {
repositories {
mavenCentral()
- jcenter()
+ maven { url "https://plugins.gradle.org/m2/" }
+ maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots' }
+ google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.3.1'
- classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
- classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7'
- classpath 'com.vanniktech:gradle-android-junit-jacoco-plugin:0.5.0'
- classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.6.3'
+ classpath 'com.android.tools.build:gradle:8.5.2'
+ classpath 'com.mxalbert.gradle:jacoco-android:0.2.0'
}
}
allprojects {
repositories {
mavenCentral()
+ google()
}
}
-
-task wrapper(type: Wrapper) {
- gradleVersion = '2.14.1'
-}
diff --git a/circle.yml b/circle.yml
deleted file mode 100644
index 494b4bc2..00000000
--- a/circle.yml
+++ /dev/null
@@ -1,59 +0,0 @@
-
-machine:
- environment:
- KEYSTORE: ${HOME}/${CIRCLE_PROJECT_REPONAME}/sigining.keystore
- PUBLISHER_ACCT_JSON_FILE: ${HOME}/${CIRCLE_PROJECT_REPONAME}/publisher_profile.json
-
-dependencies:
-
- pre:
- - if [ ! -e /usr/local/android-sdk-linux/platforms/android-25 ]; then echo y | android update sdk --all --no-ui --filter "android-25"; fi;
- - if [ ! -e /usr/local/android-sdk-linux/build-tools/25.0.2 ]; then echo y | android update sdk --all --no-ui --filter "build-tools-25.0.2"; fi;
- - bash ./misc/download_keystore.sh
- - bash ./misc/inject_circle_build_number.sh
-
-test:
-
- override:
- - (./gradlew test assembleRelease javadoc):
- timeout: 360
-
- post:
-
- # core lib:
- - cp -r ${HOME}/${CIRCLE_PROJECT_REPONAME}/androidplot-core/build/outputs/aar/ $CIRCLE_ARTIFACTS
-
- # demo app .apk:
- - cp -r ${HOME}/${CIRCLE_PROJECT_REPONAME}/demoapp/build/outputs/apk/ $CIRCLE_ARTIFACTS
-
- # javadoc:
- - cp -r ${HOME}/${CIRCLE_PROJECT_REPONAME}/androidplot-core/build/docs/javadoc/ $CIRCLE_ARTIFACTS
-
-
- # junit xml report:
- - mkdir -p $CIRCLE_TEST_REPORTS/junit-xml/
- - find . -type f -regex ".*/build/test-results/testReleaseUnitTest/.*xml" -exec cp {} $CIRCLE_TEST_REPORTS/junit-xml/ \;
-
- # junit html report:
- # TODO: recursively copy subdirs etc
- - mkdir -p $CIRCLE_TEST_REPORTS/junit-html/
- - cp -r ${HOME}/${CIRCLE_PROJECT_REPONAME}/androidplot-core/build/reports/tests/testReleaseUnitTest/* $CIRCLE_TEST_REPORTS/junit-html/
-
- # lint report:
- - mkdir -p $CIRCLE_TEST_REPORTS/lint/
- - find . -type f -regex ".*/build/outputs/.*html" -exec cp {} $CIRCLE_TEST_REPORTS/lint/ \;
-
- # code coverage:
- - ./gradlew jacocoTestReportDebug
- - mkdir -p $CIRCLE_TEST_REPORTS/jacoco/
- - cp -r ${HOME}/${CIRCLE_PROJECT_REPONAME}/androidplot-core/build/reports/jacoco/debug/. $CIRCLE_TEST_REPORTS/jacoco
- - bash <(curl -s https://codecov.io/bash)
-
-deployment:
- master:
- branch: master
- commands:
- - (./gradlew bintrayUpload):
- timeout: 360
- - bash ./misc/download_google_publisher_json.sh
- - ./gradlew publishApkRelease
diff --git a/demoapp-wearable/build.gradle b/demoapp-wearable/build.gradle
index dd1d8926..666b353e 100644
--- a/demoapp-wearable/build.gradle
+++ b/demoapp-wearable/build.gradle
@@ -16,17 +16,14 @@
buildscript {
repositories {
- jcenter()
+ google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.3.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
@@ -41,9 +38,10 @@ android {
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_6
- targetCompatibility JavaVersion.VERSION_1_6
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
}
+
buildTypes {
release {
minifyEnabled false
@@ -54,6 +52,6 @@ android {
dependencies {
compile project(':androidplot-core')
- compile 'com.google.android.support:wearable:1.3.0'
+ compile 'com.google.android.support:wearable:2.3.0'
compile 'com.google.android.gms:play-services-wearable:8.3.0'
}
diff --git a/demoapp/build.gradle b/demoapp/build.gradle
index 8702a396..2c2fbd52 100644
--- a/demoapp/build.gradle
+++ b/demoapp/build.gradle
@@ -14,37 +14,54 @@
* limitations under the License.
*/
-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'
-
- debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
- releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
- testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
-}
-
buildscript {
-
+ ext.kotlin_version = '2.0.0'
repositories {
mavenCentral()
}
-
dependencies {
- classpath 'com.github.triplet.gradle:play-publisher:1.1.5'
+ classpath 'com.github.triplet.gradle:play-publisher:3.6.0'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
+plugins {
+ id 'com.android.application'
+ id 'com.github.triplet.play' version '3.6.0'
+}
+
+apply plugin: 'kotlin-android'
+
+dependencies {
+ implementation "androidx.core:core-ktx:1.13.1"
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation project(':androidplot-core')
+ implementation 'androidx.recyclerview:recyclerview:1.3.2'
+
+ debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
+}
+
+kotlin {
+ jvmToolchain(17)
+}
+
android {
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
+ }
+
compileSdkVersion theCompileSdkVersion
- buildToolsVersion theBuildToolsVersion
+
+ buildFeatures {
+ viewBinding true
+ }
defaultConfig {
versionCode theVersionCode
versionName theVersionName
- minSdkVersion 11
+ minSdkVersion 19
targetSdkVersion theTargetSdkVersion
applicationId "com.androidplot.demos"
}
@@ -68,19 +85,18 @@ android {
debuggable true
}
}
-
-
- /**
- * TODO: uncomment this and address all the lint issues.
- */
- lintOptions {
+ lint {
abortOnError false
}
- buildToolsVersion '25.0.0'
+ namespace 'com.androidplot.demos'
+
}
play {
// see: https://github.com/Triple-T/gradle-play-publisher
- jsonFile = file(System.getenv("PUBLISHER_ACCT_JSON_FILE") ?: "publisher.json")
+ serviceAccountCredentials.set(file(System.getenv("PUBLISHER_ACCT_JSON_FILE") ?: "publisher.json"))
track = 'beta'
}
+repositories {
+ mavenCentral()
+}
diff --git a/demoapp/project.properties b/demoapp/project.properties
index 29656f2d..90a2cb18 100644
--- a/demoapp/project.properties
+++ b/demoapp/project.properties
@@ -39,5 +39,3 @@
#proguard.config=${sdk.dir}\tools\proguard\proguard-android.txt:proguard-project.txt
# using config with optimization enabled so that things like log statement removal will work:
proguard.config=${sdk.dir}/tools/proguard/proguard-android-optimize.txt:proguard-project.txt
-# Project target.
-target=android-16
diff --git a/demoapp/src/main/AndroidManifest.xml b/demoapp/src/main/AndroidManifest.xml
index 89f1428f..5b73a2b7 100644
--- a/demoapp/src/main/AndroidManifest.xml
+++ b/demoapp/src/main/AndroidManifest.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
@@ -27,75 +27,88 @@
android:icon="@drawable/ic_launcher">
+ android:label="@string/app_name"
+ android:exported="true">
+ android:label="@string/app_name"
+ android:exported="false">
+ android:label="@string/app_name"
+ android:exported="false">
+ android:label="@string/app_name"
+ android:exported="false">
+ android:label="@string/app_name"
+ android:exported="false">
+ android:label="@string/app_name"
+ android:exported="false">
+ android:label="@string/app_name"
+ android:exported="false">
+ android:label="@string/app_name"
+ android:exported="false">
+ android:screenOrientation="landscape"
+ android:exported="false">
+ android:label="@string/app_name"
+ android:exported="false">
+ android:label="@string/app_name"
+ android:exported="false">
-
+
+
@@ -111,7 +124,8 @@
+ android:name="com.androidplot.demos.widget.DemoAppWidgetProvider"
+ android:exported="false">
diff --git a/demoapp/src/main/java/com/androidplot/demos/AnimatedXYPlotActivity.java b/demoapp/src/main/java/com/androidplot/demos/AnimatedXYPlotActivity.java
index 7c2cdaee..43d3adc0 100644
--- a/demoapp/src/main/java/com/androidplot/demos/AnimatedXYPlotActivity.java
+++ b/demoapp/src/main/java/com/androidplot/demos/AnimatedXYPlotActivity.java
@@ -21,6 +21,7 @@
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
+import androidx.annotation.NonNull;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.androidplot.xy.BoundaryMode;
@@ -96,12 +97,14 @@ public void onCreate(Bundle savedInstanceState)
plot.getGraph().getLineLabelStyle(XYGraphWidget.Edge.BOTTOM).setFormat(new Format() {
@Override
- public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
+ public StringBuffer format(Object obj,
+ @NonNull StringBuffer toAppendTo,
+ @NonNull FieldPosition pos) {
int i = Math.round(((Number) obj).floatValue());
return toAppendTo.append(domainLabels[i]);
}
@Override
- public Object parseObject(String source, ParsePosition pos) {
+ public Object parseObject(String source, @NonNull ParsePosition pos) {
return null;
}
});
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/CandlestickChartActivity.java b/demoapp/src/main/java/com/androidplot/demos/CandlestickChartActivity.java
index 432b16ed..86ae1fbb 100644
--- a/demoapp/src/main/java/com/androidplot/demos/CandlestickChartActivity.java
+++ b/demoapp/src/main/java/com/androidplot/demos/CandlestickChartActivity.java
@@ -21,15 +21,28 @@
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.os.Bundle;
+import androidx.annotation.NonNull;
-import com.androidplot.*;
-import com.androidplot.util.*;
+import com.androidplot.Region;
+import com.androidplot.util.PixelUtils;
+import com.androidplot.util.SeriesUtils;
+import com.androidplot.xy.BoundaryMode;
import com.androidplot.xy.CandlestickFormatter;
import com.androidplot.xy.CandlestickMaker;
import com.androidplot.xy.CandlestickSeries;
-import com.androidplot.xy.*;
-
-import java.text.*;
+import com.androidplot.xy.CatmullRomInterpolator;
+import com.androidplot.xy.LineAndPointFormatter;
+import com.androidplot.xy.PointLabelFormatter;
+import com.androidplot.xy.PointLabeler;
+import com.androidplot.xy.StepMode;
+import com.androidplot.xy.XYGraphWidget;
+import com.androidplot.xy.XYPlot;
+import com.androidplot.xy.XYSeries;
+
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParsePosition;
/**
* A simple example of a candlestick chart rendered on an {@link XYPlot}.
@@ -98,8 +111,8 @@ public void onCreate(Bundle savedInstanceState) {
plot.setDomainStep(StepMode.INCREMENT_BY_VAL, 1);
plot.getGraph().getLineLabelStyle(XYGraphWidget.Edge.BOTTOM).setFormat(new Format() {
@Override
- public StringBuffer format(Object object, StringBuffer buffer,
- FieldPosition field) {
+ public StringBuffer format(Object object, @NonNull StringBuffer buffer,
+ @NonNull FieldPosition field) {
int day = ((Number) object).intValue() % 7;
switch (day) {
case 0:
@@ -130,7 +143,7 @@ public StringBuffer format(Object object, StringBuffer buffer,
}
@Override
- public Object parseObject(String string, ParsePosition position) {
+ public Object parseObject(String string, @NonNull ParsePosition position) {
return null;
}
});
diff --git a/demoapp/src/main/java/com/androidplot/demos/DemoApplication.java b/demoapp/src/main/java/com/androidplot/demos/DemoApplication.java
index 9c931a73..df3d6117 100644
--- a/demoapp/src/main/java/com/androidplot/demos/DemoApplication.java
+++ b/demoapp/src/main/java/com/androidplot/demos/DemoApplication.java
@@ -2,20 +2,10 @@
import android.app.*;
-import com.squareup.leakcanary.*;
-
public class DemoApplication extends Application {
@Override public void onCreate() {
super.onCreate();
- initLeakCanary();
- }
-
- protected void initLeakCanary() {
- if (LeakCanary.isInAnalyzerProcess(this)) {
- return;
- }
- LeakCanary.install(this);
}
}
diff --git a/demoapp/src/main/java/com/androidplot/demos/DualScaleActivity.java b/demoapp/src/main/java/com/androidplot/demos/DualScaleActivity.java
index 492fe332..f0116121 100644
--- a/demoapp/src/main/java/com/androidplot/demos/DualScaleActivity.java
+++ b/demoapp/src/main/java/com/androidplot/demos/DualScaleActivity.java
@@ -20,6 +20,7 @@
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.os.Bundle;
+import androidx.annotation.NonNull;
import com.androidplot.util.PixelUtils;
import com.androidplot.xy.BoundaryMode;
@@ -30,7 +31,6 @@
import com.androidplot.xy.StepMode;
import com.androidplot.xy.XYGraphWidget;
import com.androidplot.xy.XYPlot;
-import com.androidplot.xy.XYSeries;
import java.text.DecimalFormat;
import java.text.FieldPosition;
@@ -105,15 +105,15 @@ public void onCreate(Bundle savedInstanceState)
final DecimalFormat df = new DecimalFormat("$#,###");
@Override
- public StringBuffer format(Object o, StringBuffer stringBuffer,
- FieldPosition fieldPosition) {
+ public StringBuffer format(Object o, @NonNull StringBuffer stringBuffer,
+ @NonNull FieldPosition fieldPosition) {
final Number cost = costsSeries.denormalizeYVal((Number) o);
stringBuffer.append(df.format(cost.doubleValue()));
return stringBuffer;
}
@Override
- public Object parseObject(String s, ParsePosition parsePosition) {
+ public Object parseObject(String s, @NonNull ParsePosition parsePosition) {
return null;
}
});
@@ -124,15 +124,15 @@ public Object parseObject(String s, ParsePosition parsePosition) {
final DecimalFormat df = new DecimalFormat("$#.##");
@Override
- public StringBuffer format(Object o, StringBuffer stringBuffer,
- FieldPosition fieldPosition) {
+ public StringBuffer format(Object o, @NonNull StringBuffer stringBuffer,
+ @NonNull FieldPosition fieldPosition) {
Number minWage = minWageSeries.denormalizeYVal((Number) o);
stringBuffer.append(df.format(minWage.doubleValue()));
return stringBuffer;
}
@Override
- public Object parseObject(String s, ParsePosition parsePosition) {
+ public Object parseObject(String s, @NonNull ParsePosition parsePosition) {
return null;
}
});
@@ -141,8 +141,8 @@ public Object parseObject(String s, ParsePosition parsePosition) {
plot.getGraph().getLineLabelStyle(XYGraphWidget.Edge.BOTTOM).setFormat(new Format() {
@Override
- public StringBuffer format(Object o, StringBuffer stringBuffer,
- FieldPosition fieldPosition) {
+ public StringBuffer format(Object o, @NonNull StringBuffer stringBuffer,
+ @NonNull FieldPosition fieldPosition) {
Number year = ((Number) o).intValue() + 2012;
stringBuffer.append(year);
@@ -150,7 +150,7 @@ public StringBuffer format(Object o, StringBuffer stringBuffer,
}
@Override
- public Object parseObject(String s, ParsePosition parsePosition) {
+ public Object parseObject(String s, @NonNull ParsePosition parsePosition) {
return null;
}
});
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 47acf1cb..290b7802 100644
--- a/demoapp/src/main/java/com/androidplot/demos/ListViewActivity.java
+++ b/demoapp/src/main/java/com/androidplot/demos/ListViewActivity.java
@@ -20,15 +20,21 @@
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
+import androidx.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
+
import com.androidplot.Plot;
import com.androidplot.ui.SeriesBundle;
import com.androidplot.util.PixelUtils;
-import com.androidplot.xy.*;
+import com.androidplot.xy.CatmullRomInterpolator;
+import com.androidplot.xy.LineAndPointFormatter;
+import com.androidplot.xy.SimpleXYSeries;
+import com.androidplot.xy.XYPlot;
+import com.androidplot.xy.XYSeries;
import java.util.ArrayList;
import java.util.List;
@@ -72,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:
@@ -100,8 +106,9 @@ public int getCount() {
return NUM_PLOTS;
}
+ @NonNull
@Override
- public View getView(int pos, View convertView, ViewGroup parent) {
+ public View getView(int pos, View convertView, @NonNull ViewGroup parent) {
LayoutInflater inf = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = convertView;
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..6632af40
--- /dev/null
+++ b/demoapp/src/main/java/com/androidplot/demos/MainActivity.kt
@@ -0,0 +1,109 @@
+/*
+ * 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 android.content.Intent
+import com.androidplot.demos.databinding.MainBinding
+
+class MainActivity : Activity() {
+
+ private lateinit var binding: MainBinding
+
+ public override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = MainBinding.inflate(layoutInflater)
+
+ 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/java/com/androidplot/demos/SimplePieChartActivity.java b/demoapp/src/main/java/com/androidplot/demos/SimplePieChartActivity.java
index 03b5b12b..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)
{
@@ -62,6 +64,9 @@ public void onCreate(Bundle savedInstanceState)
// initialize our XYPlot reference:
pie = (PieChart) findViewById(R.id.mySimplePieChart);
+ // enable the legend:
+ pie.getLegend().setVisible(true);
+
final float padding = PixelUtils.dpToPix(30);
pie.getPie().setPadding(padding, padding, padding, padding);
@@ -183,36 +188,10 @@ protected void setupIntroAnimation() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float scale = valueAnimator.getAnimatedFraction();
-// scalingSeries1.setScale(scale);
-// scalingSeries2.setScale(scale);
renderer.setExtentDegs(360 * scale);
pie.redraw();
}
});
-// animator.addListener(new Animator.AnimatorListener() {
-// @Override
-// public void onAnimationStart(Animator animator) {
-//
-// }
-//
-// @Override
-// public void onAnimationEnd(Animator animator) {
-// // the animation is over, so show point labels:
-// series1Format.getPointLabelFormatter().getTextPaint().setColor(Color.WHITE);
-// series2Format.getPointLabelFormatter().getTextPaint().setColor(Color.WHITE);
-// plot.redraw();
-// }
-//
-// @Override
-// public void onAnimationCancel(Animator animator) {
-//
-// }
-//
-// @Override
-// public void onAnimationRepeat(Animator animator) {
-//
-// }
-// });
// the animation will run for 1.5 seconds:
animator.setDuration(1500);
diff --git a/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java b/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java
index 8cff7fed..db3a5a28 100644
--- a/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java
+++ b/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 AndroidPlot.com
+ * Copyright 2018 AndroidPlot.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,19 +15,24 @@
*/
package com.androidplot.demos;
+
import android.app.Activity;
-import android.graphics.*;
+import android.graphics.DashPathEffect;
import android.os.Bundle;
+import androidx.annotation.NonNull;
import com.androidplot.util.PixelUtils;
+import com.androidplot.xy.CatmullRomInterpolator;
+import com.androidplot.xy.LineAndPointFormatter;
import com.androidplot.xy.SimpleXYSeries;
+import com.androidplot.xy.XYGraphWidget;
+import com.androidplot.xy.XYPlot;
import com.androidplot.xy.XYSeries;
-import com.androidplot.xy.*;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
-import java.util.*;
+import java.util.Arrays;
/**
* A simple XYPlot
@@ -43,7 +48,7 @@ public void onCreate(Bundle savedInstanceState)
setContentView(R.layout.simple_xy_plot_example);
// initialize our XYPlot reference:
- plot = (XYPlot) findViewById(R.id.plot);
+ plot = findViewById(R.id.plot);
// create a couple arrays of y-values to plot:
final Number[] domainLabels = {1, 2, 3, 6, 7, 8, 9, 10, 13, 14};
@@ -86,12 +91,12 @@ public void onCreate(Bundle savedInstanceState)
plot.getGraph().getLineLabelStyle(XYGraphWidget.Edge.BOTTOM).setFormat(new Format() {
@Override
- public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
+ public StringBuffer format(Object obj, @NonNull StringBuffer toAppendTo, @NonNull FieldPosition pos) {
int i = Math.round(((Number) obj).floatValue());
return toAppendTo.append(domainLabels[i]);
}
@Override
- public Object parseObject(String source, ParsePosition pos) {
+ public Object parseObject(String source, @NonNull ParsePosition pos) {
return null;
}
});
diff --git a/demoapp/src/main/java/com/androidplot/demos/StepChartExampleActivity.java b/demoapp/src/main/java/com/androidplot/demos/StepChartExampleActivity.java
index 5caf8651..4b1fdc35 100644
--- a/demoapp/src/main/java/com/androidplot/demos/StepChartExampleActivity.java
+++ b/demoapp/src/main/java/com/androidplot/demos/StepChartExampleActivity.java
@@ -18,12 +18,20 @@
import android.app.Activity;
-import android.graphics.*;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.Shader;
import android.os.Bundle;
+import androidx.annotation.NonNull;
+
import com.androidplot.util.PixelUtils;
import com.androidplot.xy.SimpleXYSeries;
+import com.androidplot.xy.StepFormatter;
+import com.androidplot.xy.StepMode;
+import com.androidplot.xy.XYGraphWidget;
+import com.androidplot.xy.XYPlot;
import com.androidplot.xy.XYSeries;
-import com.androidplot.xy.*;
import java.text.DecimalFormat;
import java.text.FieldPosition;
@@ -82,8 +90,8 @@ public void onCreate(Bundle savedInstanceState)
// create a custom getFormatter to draw our state names as range tick labels:
mySimpleXYPlot.getGraph().getLineLabelStyle(XYGraphWidget.Edge.LEFT).setFormat(new Format() {
@Override
- public StringBuffer format(Object obj, StringBuffer toAppendTo,
- FieldPosition pos) {
+ public StringBuffer format(Object obj, @NonNull StringBuffer toAppendTo,
+ @NonNull FieldPosition pos) {
Number num = (Number) obj;
switch (num.intValue()) {
case 1:
@@ -106,7 +114,7 @@ public StringBuffer format(Object obj, StringBuffer toAppendTo,
}
@Override
- public Object parseObject(String source, ParsePosition pos) {
+ public Object parseObject(String source, @NonNull ParsePosition pos) {
return null;
}
});
diff --git a/demoapp/src/main/java/com/androidplot/demos/TimeSeriesActivity.java b/demoapp/src/main/java/com/androidplot/demos/TimeSeriesActivity.java
index 684d9388..12930bd7 100644
--- a/demoapp/src/main/java/com/androidplot/demos/TimeSeriesActivity.java
+++ b/demoapp/src/main/java/com/androidplot/demos/TimeSeriesActivity.java
@@ -16,16 +16,27 @@
package com.androidplot.demos;
+import android.annotation.SuppressLint;
import android.app.Activity;
-import android.graphics.*;
+import android.graphics.Color;
+import android.graphics.DashPathEffect;
+import android.graphics.Paint;
import android.os.Bundle;
+import androidx.annotation.NonNull;
import com.androidplot.util.PixelUtils;
+import com.androidplot.xy.BoundaryMode;
+import com.androidplot.xy.LineAndPointFormatter;
import com.androidplot.xy.SimpleXYSeries;
-import com.androidplot.xy.XYSeries;
-import com.androidplot.xy.*;
-
-import java.text.*;
+import com.androidplot.xy.StepMode;
+import com.androidplot.xy.XYGraphWidget;
+import com.androidplot.xy.XYPlot;
+
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
@@ -92,10 +103,13 @@ 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
- public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
+ public StringBuffer format(Object obj,
+ @NonNull StringBuffer toAppendTo,
+ @NonNull FieldPosition pos) {
// this rounding is necessary to avoid precision loss when converting from
// double back to int:
@@ -104,7 +118,7 @@ public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition po
}
@Override
- public Object parseObject(String source, ParsePosition pos) {
+ public Object parseObject(String source, @NonNull ParsePosition pos) {
return null;
}
diff --git a/demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java b/demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java
index 94d1efa5..dfae19d9 100644
--- a/demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java
+++ b/demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java
@@ -41,14 +41,14 @@ public class TouchZoomExampleActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.touch_zoom_example);
- resetButton = (Button) findViewById(R.id.resetButton);
+ resetButton = findViewById(R.id.resetButton);
resetButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
reset();
}
});
- plot = (XYPlot) findViewById(R.id.plot);
+ plot = findViewById(R.id.plot);
// set a fixed origin and a "by-value" step mode so that grid lines will
// move dynamically with the data when the users pans or zooms:
@@ -63,8 +63,8 @@ public void onClick(View view) {
plot.setRangeStepModel( new StepModelFit(plot.getBounds().getyRegion(),inc_range,NUM_GRIDLINES));
- panSpinner = (Spinner) findViewById(R.id.pan_spinner);
- zoomSpinner = (Spinner) findViewById(R.id.zoom_spinner);
+ panSpinner = findViewById(R.id.pan_spinner);
+ zoomSpinner = findViewById(R.id.zoom_spinner);
plot.getGraph().setLinesPerRangeLabel(2);
plot.getGraph().setLinesPerDomainLabel(2);
plot.getGraph().getBackgroundPaint().setColor(Color.TRANSPARENT);
@@ -152,6 +152,7 @@ public void onNothingSelected(AdapterView> parent) {
}
});
+
zoomSpinner.setAdapter(
new ArrayAdapter<>(this, R.layout.spinner_item, PanZoom.Zoom.values()));
zoomSpinner.setSelection(panZoom.getZoom().ordinal());
@@ -167,5 +168,19 @@ public void onNothingSelected(AdapterView> parent) {
}
});
}
+
+ // (optional) save the current pan/zoom state
+ @Override
+ public void onSaveInstanceState(Bundle bundle) {
+ bundle.putSerializable("pan-zoom-state", panZoom.getState());
+ }
+
+ // (optional) restore the previously saved pan/zoom state
+ @Override
+ public void onRestoreInstanceState(Bundle bundle) {
+ PanZoom.State state = (PanZoom.State) bundle.getSerializable("pan-zoom-state");
+ panZoom.setState(state);
+ plot.redraw();
+ }
}
diff --git a/demoapp/src/main/java/com/androidplot/demos/XYRegionExampleActivity.java b/demoapp/src/main/java/com/androidplot/demos/XYRegionExampleActivity.java
index c9cc953d..0f48c3c2 100644
--- a/demoapp/src/main/java/com/androidplot/demos/XYRegionExampleActivity.java
+++ b/demoapp/src/main/java/com/androidplot/demos/XYRegionExampleActivity.java
@@ -69,8 +69,8 @@ public class XYRegionExampleActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.xyregion_example);
- plot = (XYPlot) findViewById(R.id.xyRegionExamplePlot);
- timCB = (CheckBox) findViewById(R.id.s1CheckBox);
+ plot = findViewById(R.id.xyRegionExamplePlot);
+ timCB = findViewById(R.id.s1CheckBox);
timCB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
@@ -78,7 +78,7 @@ public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
}
});
- nickCB = (CheckBox) findViewById(R.id.s2CheckBox);
+ nickCB = findViewById(R.id.s2CheckBox);
nickCB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
@@ -86,7 +86,7 @@ public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
}
});
- r2CheckBox = (CheckBox) findViewById(R.id.r2CheckBox);
+ r2CheckBox = findViewById(R.id.r2CheckBox);
r2CheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
@@ -94,7 +94,7 @@ public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
}
});
- r3CheckBox = (CheckBox) findViewById(R.id.r3CheckBox);
+ r3CheckBox = findViewById(R.id.r3CheckBox);
r3CheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
@@ -102,7 +102,7 @@ public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
}
});
- r4CheckBox = (CheckBox) findViewById(R.id.r4CheckBox);
+ r4CheckBox = findViewById(R.id.r4CheckBox);
r4CheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
diff --git a/demoapp/src/main/java/com/androidplot/demos/widget/DemoAppWidgetProvider.java b/demoapp/src/main/java/com/androidplot/demos/widget/DemoAppWidgetProvider.java
index fe3627b3..592fbce3 100644
--- a/demoapp/src/main/java/com/androidplot/demos/widget/DemoAppWidgetProvider.java
+++ b/demoapp/src/main/java/com/androidplot/demos/widget/DemoAppWidgetProvider.java
@@ -25,6 +25,8 @@
import android.widget.RemoteViews;
import com.androidplot.demos.R;
import com.androidplot.ui.*;
+import com.androidplot.util.PixelUtils;
+import com.androidplot.xy.XYGraphWidget;
import com.androidplot.xy.XYSeries;
import com.androidplot.xy.LineAndPointFormatter;
import com.androidplot.xy.SimpleXYSeries;
@@ -43,10 +45,6 @@ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] a
plot.getGraph().setMargins(0, 0, 0 , 0);
plot.getGraph().setPadding(0, 0, 0, 0);
-// plot.getGraphWidget().getDomainLineLabelPaint().setTextSize(PixelUtils.spToPix(10));
-// plot.getGraphWidget().getRangeLineLabelPaint().setTextSize(PixelUtils.spToPix(10));
-// plot.getGraphWidget().getDomainOriginLineLabelPaint().setTextSize(PixelUtils.spToPix(10));
-// plot.getGraphWidget().getRangeOriginLineLabelPaint().setTextSize(PixelUtils.spToPix(10));
plot.getGraph().position(0, HorizontalPositioning.ABSOLUTE_FROM_LEFT, 0,
VerticalPositioning.ABSOLUTE_FROM_TOP, Anchor.LEFT_TOP);
@@ -55,6 +53,15 @@ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] a
plot.getLayoutManager().moveToTop(plot.getTitle());
+ plot.getGraph().setLineLabelEdges(XYGraphWidget.Edge.LEFT, XYGraphWidget.Edge.BOTTOM);
+ plot.getGraph().getLineLabelInsets().setLeft(PixelUtils.dpToPix(16));
+ plot.getGraph().getLineLabelInsets().setBottom(PixelUtils.dpToPix(4));
+ plot.getGraph().getLineLabelStyle(XYGraphWidget.Edge.LEFT).getPaint().setColor(Color.RED);
+ plot.getGraph().getGridInsets().setTop(PixelUtils.dpToPix(12));
+ plot.getGraph().getGridInsets().setRight(PixelUtils.dpToPix(12));
+ plot.getGraph().getGridInsets().setLeft(PixelUtils.dpToPix(36));
+ plot.getGraph().getGridInsets().setBottom(PixelUtils.dpToPix(16));
+
plot.measure(w, h);
plot.layout(0, 0, w, h);
diff --git a/demoapp/src/main/res/layout/bar_plot_example.xml b/demoapp/src/main/res/layout/bar_plot_example.xml
index e607f973..3182342d 100644
--- a/demoapp/src/main/res/layout/bar_plot_example.xml
+++ b/demoapp/src/main/res/layout/bar_plot_example.xml
@@ -1,5 +1,4 @@
-
-
+ xmlns:ap="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ ap:title="Growth" />
+ android:layout_height="wrap_content" />
+ android:layout_height="wrap_content" />
+ android:layout_height="wrap_content" />
+ android:progress="10" />
+ android:progress="1" />
+ android:checked="true"
+ android:text="Series 1" />
+ android:checked="true"
+ android:text="Series 2" />
\ No newline at end of file
diff --git a/demoapp/src/main/res/layout/demo_app_widget.xml b/demoapp/src/main/res/layout/demo_app_widget.xml
index 85b5fe6b..81977453 100644
--- a/demoapp/src/main/res/layout/demo_app_widget.xml
+++ b/demoapp/src/main/res/layout/demo_app_widget.xml
@@ -17,14 +17,13 @@
-->
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
-
-
+
\ No newline at end of file
diff --git a/demoapp/src/main/res/layout/dynamic_xyplot_example.xml b/demoapp/src/main/res/layout/dynamic_xyplot_example.xml
index ddfd0cba..5b0c5644 100644
--- a/demoapp/src/main/res/layout/dynamic_xyplot_example.xml
+++ b/demoapp/src/main/res/layout/dynamic_xyplot_example.xml
@@ -1,5 +1,4 @@
-
-
+ xmlns:ap="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ ap:legendTextSize="15sp"
+ ap:rangeTitle="Range"
+ ap:title="A Dynamic XY Plot" />
diff --git a/demoapp/src/main/res/layout/main.xml b/demoapp/src/main/res/layout/main.xml
index 65a43ef9..3a2d873f 100644
--- a/demoapp/src/main/res/layout/main.xml
+++ b/demoapp/src/main/res/layout/main.xml
@@ -32,99 +32,124 @@
+
+
diff --git a/demoapp/src/main/res/layout/pie_chart.xml b/demoapp/src/main/res/layout/pie_chart.xml
index e6fd0b0c..3722d9e2 100644
--- a/demoapp/src/main/res/layout/pie_chart.xml
+++ b/demoapp/src/main/res/layout/pie_chart.xml
@@ -16,7 +16,8 @@
-->
-
+
-
+
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/demoapp/src/main/res/values-ldpi/dimens.xml b/demoapp/src/main/res/layout/recyclerview_example_item.xml
similarity index 69%
rename from demoapp/src/main/res/values-ldpi/dimens.xml
rename to demoapp/src/main/res/layout/recyclerview_example_item.xml
index a58e6c64..b8392dc9 100644
--- a/demoapp/src/main/res/values-ldpi/dimens.xml
+++ b/demoapp/src/main/res/layout/recyclerview_example_item.xml
@@ -1,4 +1,5 @@
+
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/demoapp/src/main/res/layout/simple_xy_plot_example.xml b/demoapp/src/main/res/layout/simple_xy_plot_example.xml
index fecf1241..08a2c5af 100644
--- a/demoapp/src/main/res/layout/simple_xy_plot_example.xml
+++ b/demoapp/src/main/res/layout/simple_xy_plot_example.xml
@@ -18,16 +18,19 @@
+ android:layout_width="match_parent"
+ android:orientation="vertical">
+ ap:lineLabelRotationBottom="-45"
+ android:layout_weight="1"/>
+
\ No newline at end of file
diff --git a/demoapp/src/main/res/layout/step_chart_example.xml b/demoapp/src/main/res/layout/step_chart_example.xml
index 9ce501ab..d72dc051 100644
--- a/demoapp/src/main/res/layout/step_chart_example.xml
+++ b/demoapp/src/main/res/layout/step_chart_example.xml
@@ -1,5 +1,4 @@
-
-
+ xmlns:ap="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ android:id="@+id/stepChartExamplePlot"
+ style="@style/APDefacto.Dark"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ ap:domainTitle="time (secs)"
+ ap:rangeTitle="server state"
+ ap:title="HTTP Server State (15 Sec)" />
diff --git a/demoapp/src/main/res/layout/time_series_example.xml b/demoapp/src/main/res/layout/time_series_example.xml
index 2bcaa251..37b59957 100644
--- a/demoapp/src/main/res/layout/time_series_example.xml
+++ b/demoapp/src/main/res/layout/time_series_example.xml
@@ -17,27 +17,27 @@
-->
+ xmlns:ap="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ android:id="@+id/plot1"
+ style="@style/APDefacto.Light"
+ renderMode="use_main_thread"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ ap:backgroundColor="@color/ap_white"
+ ap:graphBackgroundColor="@color/ap_white"
+ ap:gridInsetBottom="40dp"
+ ap:gridInsetLeft="35dp"
+ ap:lineLabelInsetBottom="25dp"
+ ap:lineLabelInsetLeft="25dp"
+ ap:lineLabelRotationBottom="-45"
+ ap:paddingBottom="10dp"
+ ap:paddingLeft="10dp"
+ ap:paddingRight="10dp"
+ ap:paddingTop="10dp"
+ ap:title="@string/ts_plot1_title" />
\ No newline at end of file
diff --git a/demoapp/src/main/res/layout/touch_zoom_example.xml b/demoapp/src/main/res/layout/touch_zoom_example.xml
index 654b0c67..5f3bd5da 100644
--- a/demoapp/src/main/res/layout/touch_zoom_example.xml
+++ b/demoapp/src/main/res/layout/touch_zoom_example.xml
@@ -1,5 +1,4 @@
-
-
+ xmlns:ap="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/ap_black"
+ android:orientation="vertical">
+ ap:renderMode="use_background_thread"
+ ap:title="A Simple XY Plot" />
+ android:layout_weight="5"
+ android:orientation="vertical">
+ android:text="Pan"
+ android:textSize="12sp" />
+ android:layout_height="wrap_content"
+ android:padding="0dp">
@@ -63,29 +63,30 @@
+ android:layout_weight="3"
+ android:orientation="vertical">
+ android:text="Zoom"
+ android:textSize="12sp" />
+ android:layout_height="wrap_content"
+ android:padding="0dp">
+
+ android:text="Reset" />
\ No newline at end of file
diff --git a/demoapp/src/main/res/values-hdpi/dimens.xml b/demoapp/src/main/res/values-hdpi/dimens.xml
index df9a1f9f..2c320eef 100644
--- a/demoapp/src/main/res/values-hdpi/dimens.xml
+++ b/demoapp/src/main/res/values-hdpi/dimens.xml
@@ -1,21 +1,3 @@
-
-
-
-
30dp
20dp
diff --git a/demoapp/src/main/res/values/dimens.xml b/demoapp/src/main/res/values/dimens.xml
index a4ea138d..23de7062 100644
--- a/demoapp/src/main/res/values/dimens.xml
+++ b/demoapp/src/main/res/values/dimens.xml
@@ -1,21 +1,3 @@
-
-
-
-
diff --git a/docs/attrs.md b/docs/attrs.md
new file mode 100644
index 00000000..afda917c
--- /dev/null
+++ b/docs/attrs.md
@@ -0,0 +1,598 @@
+_This documentation is auto generated from [attrs.xml](../androidplot-core/src/main/res/values/attrs.xml)._
+
+# Androidplot XML Attributes
+Attributes are broken down by element followed by either their type or list of accepted values.
+Supported Elements:
+
+* [Plot](#Plot)
+* [XYPlot](#XYPlot)
+* [PieChart](#PieChart)
+
+## Plot
+Plot's attrs are available in all Plot types.
+
+### markupEnabled
+__boolean__
+
+### renderMode
+* use_background_thread
+* use_main_thread
+
+### marginTop
+__dimension__
+
+### marginBottom
+__dimension__
+
+### marginLeft
+__dimension__
+
+### marginRight
+__dimension__
+
+### paddingTop
+__dimension__
+
+### paddingBottom
+__dimension__
+
+### paddingLeft
+__dimension__
+
+### paddingRight
+__dimension__
+
+### title
+__dimension__
+
+### titleTextSize
+__string__
+
+### titleTextColor
+__dimension__
+
+### backgroundColor
+__color__
+
+### borderColor
+__color__
+
+### borderThickness
+__color__
+
+## XYPlot
+XML attributes for the [XYPlot](xyplot.md) class.
+
+### previewMode
+TODO
+
+### domainStepMode
+* subdivide
+* increment_by_val
+* increment_by_pixels
+
+### domainStep
+__dimension|float|integer__
+
+### rangeStepMode
+* subdivide
+* increment_by_val
+* increment_by_pixels
+
+### rangeStep
+__dimension|float|integer__
+
+### domainTitle
+__string__
+
+### domainTitleTextColor
+__color__
+
+### domainTitleTextSize
+__dimension__
+
+### domainTitleHeightMode
+* absolute
+* relative
+* fill
+
+### domainTitleWidthMode
+* absolute
+* relative
+* fill
+
+### domainTitleHeight
+__dimension|float|integer__
+
+### domainTitleWidth
+__dimension|float|integer__
+
+### domainTitleHorizontalPositioning
+* absolute_from_left
+* absolute_from_right
+* absolute_from_center
+* relative_from_left
+* relative_from_right
+* relative_from_center
+
+`HorizontalPositioning` component of the `HorizontalPosition` of the `TextLabelWidget`
+that displays the domain title.
+See [Positioning Widgets](plot_composition.md#positioning-widgets) documentation.
+
+### domainTitleVerticalPositioning
+* absolute_from_top
+* absolute_from_bottom
+* absolute_from_center
+* relative_from_top
+* relative_from_bottom
+* relative_from_center
+
+`VerticalPositioning` component of the `VerticalPosition` of the `TextLabelWidget`
+that displays the domain title.
+See [Positioning Widgets](plot_composition.md#positioning-widgets) documentation.
+
+### domainTitleHorizontalPosition
+__dimension|float|integer__
+
+`float` value component of the `HorizontalPosition` of the `TextLabelWidget`
+that displays the domain title.
+See [Positioning Widgets](plot_composition.md#positioning-widgets) documentation.
+
+### domainTitleVerticalPosition
+__dimension|float|integer__
+
+`float` value component of the `VerticalPosition` of the `TextLabelWidget`
+that displays the domain title.
+See [Positioning Widgets](plot_composition.md#positioning-widgets) documentation.
+
+### domainTitleAnchor
+* top_middle
+* left_top
+* left_middle
+* left_bottom
+* right_top
+* right_middle
+* right_bottom
+* bottom_middle
+* center
+
+### domainTitleVisible
+__boolean__
+
+### rangeTitle
+__string__
+
+### rangeTitleTextColor
+__color__
+
+### rangeTitleTextSize
+__dimension__
+
+### rangeTitleHeightMode
+* absolute
+* relative
+* fill
+
+### rangeTitleWidthMode
+* absolute
+* relative
+* fill
+
+### rangeTitleHeight
+__dimension|float|integer__
+
+### rangeTitleWidth
+__dimension|float|integer__
+
+### rangeTitleHorizontalPositioning
+* absolute_from_left
+* absolute_from_right
+* absolute_from_center
+* relative_from_left
+* relative_from_right
+* relative_from_center
+
+### rangeTitleVerticalPositioning
+* absolute_from_top
+* absolute_from_bottom
+* absolute_from_center
+* relative_from_top
+* relative_from_bottom
+* relative_from_center
+
+### rangeTitleHorizontalPosition
+__dimension|float|integer__
+
+### rangeTitleVerticalPosition
+__dimension|float|integer__
+
+### rangeTitleAnchor
+* top_middle
+* left_top
+* left_middle
+* left_bottom
+* right_top
+* right_middle
+* right_bottom
+* bottom_middle
+* center
+
+### rangeTitleVisible
+__boolean__
+
+### drawGridOnTop
+__boolean__
+
+(default is false) When set to true, grid lines are drawn on top of rendered series data
+instead of underneath.
+
+### graphHeightMode
+* absolute
+* relative
+* fill
+
+### graphWidthMode
+* absolute
+* relative
+* fill
+
+### graphHeight
+__dimension|float|integer__
+
+### graphWidth
+__dimension|float|integer__
+
+### graphRotation
+* none
+* ninety_degrees
+* negative_ninety_degrees
+* one_hundred_eighty_degrees
+
+### graphHorizontalPositioning
+* absolute_from_left
+* absolute_from_right
+* absolute_from_center
+* relative_from_left
+* relative_from_right
+* relative_from_center
+
+`HorizontalPositioning` component of the `HorizontalPosition` of the `XYGraphWidget`.
+See [Positioning Widgets](plot_composition.md#positioning-widgets) documentation.
+
+### graphVerticalPositioning
+* absolute_from_top
+* absolute_from_bottom
+* absolute_from_center
+* relative_from_top
+* relative_from_bottom
+* relative_from_center
+
+`VerticalPositioning` component of the `VerticalPosition` of the `XYGraphWidget`.
+See [Positioning Widgets](plot_composition.md#positioning-widgets) documentation.
+
+### graphHorizontalPosition
+__dimension|float|integer__
+
+`float` value component of the `HorizontalPosition` of the `XYGraphWidget`.
+See [Positioning Widgets](plot_composition.md#positioning-widgets) documentation.
+
+### graphVerticalPosition
+__dimension|float|integer__
+
+`float` value component of the `VerticalPosition` of the `XYGraphWidget`.
+See [Positioning Widgets](plot_composition.md#positioning-widgets) documentation.
+
+### graphAnchor
+* top_middle
+* left_top
+* left_middle
+* left_bottom
+* right_top
+* right_middle
+* right_bottom
+* bottom_middle
+* center
+
+### graphVisible
+__boolean__
+
+### graphMarginTop
+__dimension__
+
+### graphMarginBottom
+__dimension__
+
+### graphMarginLeft
+__dimension__
+
+### graphMarginRight
+__dimension__
+
+### graphPaddingTop
+__dimension__
+
+### graphPaddingBottom
+__dimension__
+
+### graphPaddingLeft
+__dimension__
+
+### graphPaddinRight
+__dimension__
+
+### gridClippingEnabled
+__boolean__
+
+### gridInsetTop
+__dimension__
+
+### gridInsetBottom
+__dimension__
+
+### gridInsetLeft
+__dimension__
+
+### gridInsetRight
+__dimension__
+
+### lineLabelInsetTop
+__dimension__
+
+Top edge of a rectangle relative to the XYGraphWidget
+border upon which line labels will be anchored.
+
+### lineLabelInsetBottom
+__dimension__
+
+Bottom edge of a rectangle relative to the XYGraphWidget
+border upon which line labels will be anchored.
+
+### lineLabelInsetLeft
+__dimension__
+
+Left edge of a rectangle relative to the XYGraphWidget
+border upon which line labels will be anchored.
+
+### lineLabelInsetRight
+__dimension__
+
+Right edge of a rectangle relative to the XYGraphWidget
+border upon which line labels will be anchored.
+
+### lineLabels
+* top
+* bottom
+* left
+* right
+
+Enable line labels on one or more edge of the graph. For example, to enable labels on the left
+and bottom edges:
+```
+ap:lineLabels="left|bottom"
+```
+
+### lineLabelAlignTop
+* left
+* center
+* right
+
+Text alignment of line labels drawn on top edge of the plot. This alignment is applied relative
+to the line label insets defined by `lineLabelInsetTop`.
+
+### lineLabelAlignBottom
+* left
+* center
+* right
+
+Text alignment of line labels drawn on bottom edge of the plot. This alignment is applied relative
+to the line label insets defined by `lineLabelInsetBottom`.
+
+### lineLabelAlignLeft
+* left
+* center
+* right
+
+Text alignment of line labels drawn on left edge of the plot. This alignment is applied relative
+to the line label insets defined by `lineLabelInsetLeft`.
+
+### lineLabelAlignRight
+* left
+* center
+* right
+
+Text alignment of line labels drawn on right edge of the plot. This alignment is applied relative
+to the line label insets defined by `lineLabelInsetRight`.
+
+### lineLabelRotationTop
+__float__
+
+Angle at which line labels on the plot's top edge are drawn.
+
+### lineLabelRotationBottom
+__float__
+
+Angle at which line labels on the plot's bottom edge are drawn.
+
+### lineLabelRotationLet
+__float__
+
+Angle at which line labels on the plot's left edge are drawn.
+
+### lineLabelRotationRight
+__float__
+
+Angle at which line labels on the plot's right edge are drawn.
+
+### domainLineThickness
+__dimension__
+
+### rangeLineThickness
+__dimension__
+
+### domainLineColor
+__color__
+
+### rangeLineColor
+__color__
+
+### domainOriginLineThickness
+__dimension__
+
+### rangeOriginLineThickness
+__dimension__
+
+### domainOriginLineColor
+__color__
+
+### rangeOriginLineColor
+__color__
+
+### lineLabelTextSizeTop
+__dimension__
+
+### lineLabelTextSizeBottom
+__dimension__
+
+### lineLabelTextSizeLeft
+__dimension__
+
+### lineLabelTextSizeRight
+__dimension__
+
+### lineLabelTextColorTop
+__color__
+
+### lineLabelTextColorBottom
+__color__
+
+### lineLabelTextColorLeft
+__color__
+
+### lineLabelTextColorRight
+__color__
+
+### lineExtensionTop
+__dimension__
+
+### lineExtensionBottom
+__dimension__
+
+### lineExtensionLeft
+__dimension__
+
+### lineExtensionRight
+__dimension__
+
+### gridBackgroundColor
+__color__
+
+background color of the grid portion of the XYGraphWidget
+
+### graphBackgroundColor
+__color__
+
+background color of the XYGraphWidget
+
+### legendHeightMode
+* absolute
+* relative
+* fill
+
+### legendWidthMode
+* absolute
+* relative
+* fill
+
+### legendHeight
+__dimension|float|integer__
+
+### legendWidth
+__dimension|float|integer__
+
+### legendHorizontalPositioning
+`HorizontalPositioning` component of the `HorizontalPosition` of the `XYLegendWidget`.
+See [Positioning Widgets](plot_composition.md#positioning-widgets) documentation.
+* absolute_from_left
+* absolute_from_right
+* absolute_from_center
+* relative_from_left
+* relative_from_right
+* relative_from_center
+
+### legendVerticalPositioning
+`VerticalPositioning` component of the `VerticalPosition` of the `XYLegendWidget`.
+See [Positioning Widgets](plot_composition.md#positioning-widgets) documentation.
+* absolute_from_top
+* absolute_from_bottom
+* absolute_from_center
+* relative_from_top
+* relative_from_bottom
+* relative_from_center
+
+### legendHorizontalPosition
+__dimension|float|integer__
+
+`float` value component of the `HorizontalPosition` of the `XYLegendWidget`.
+See [Positioning Widgets](plot_composition.md#positioning-widgets) documentation.
+
+### legendVerticalPosition
+__dimension|float|integer__
+
+`float` value component of the `VerticalPosition` of the `XYLegendWidget`.
+See [Positioning Widgets](plot_composition.md#positioning-widgets) documentation.
+
+### legendAnchor
+* top_middle
+* left_top
+* left_middle
+* left_bottom
+* right_top
+* right_middle
+* right_bottom
+* bottom_middle
+* center
+
+### legendTextSize
+__dimension__
+
+### legendTextColor
+__color__
+
+### legendIconHeightMode
+* absolute
+* relative
+* fill
+
+### legendIconWidthMode
+* absolute
+* relative
+* fill
+
+### legendIconHeight
+__dimension|float|integer__
+
+### legendIconWidth
+__dimension|float|integer__
+
+### legendVisible
+__boolean__
+
+### pieBorderThickness
+__dimension__
+
+Determines how far beyond the graph's edge each domain grid line will extend.
+
+### pieBorderThickness
+__dimension__
+
+Determines how far beyond the graph's edge each range grid line will extend.
+
+## PieChart
+TODO
+
+### pieBorderColor
+__color__
+
+### pieBorderThickness
+__dimension__
+
diff --git a/docs/barchart.md b/docs/barchart.md
index 7da49957..5b384250 100644
--- a/docs/barchart.md
+++ b/docs/barchart.md
@@ -118,14 +118,14 @@ The exact size of the `BarGroup` is specified in pixels and the space between ea
is dynamically calculated based on that size.
```java
-barRenderer.setBarWidth(BarRenderer.BarGroupWidthMode.FIXED_WIDTH, PixelUtils.dpToPix(25));
+barRenderer.setBarGroupWidth(BarRenderer.BarGroupWidthMode.FIXED_WIDTH, PixelUtils.dpToPix(25));
```
##### FIXED_GAP
The exact size of the "gap" between each `BarGroup` is specified in pixels and the size of each `BarGroup`
is dynamically calculated based on that spacing.
```java
-barRenderer.setBarWidth(BarRenderer.BarGroupWidthMode.FIXED_GAP, PixelUtils.dpToPix(5));
+barRenderer.setBarGroupWidth(BarRenderer.BarGroupWidthMode.FIXED_GAP, PixelUtils.dpToPix(5));
```
# Example
diff --git a/docs/custom_renderer.md b/docs/custom_renderer.md
new file mode 100644
index 00000000..73cac354
--- /dev/null
+++ b/docs/custom_renderer.md
@@ -0,0 +1,101 @@
+# Custom Renderers
+Androidplot renderers can be extended with extra functionality in the form of custom renderers.
+For the example below we'll look at creating a custom implementation of BarRenderer that will
+draw bars with rounded edges. While this example demonstrates an `XYPlot`, the steps apply to
+`PieChart` etc. as well.
+
+## Create the Formatter
+The first step to creating a custom renderer is defining it's `Formatter`. Aside from providing
+the visual configuration used by the `Renderer` to draw series data, it's also used by Androidplot
+to map a series to a specific renderer type and if necessary, obtain a new instance of that renderer.
+Here's the most basic implementation:
+
+```java
+class RoundedBarFormatter extends BarFormatter {
+
+ {
+ // for now we'll hardcode some formatting values.
+ // a real implementation would probably provide a constructor instead
+ getBorderPaint().setColor(Color.WHITE);
+ getFillPaint().setColor(Color.RED);
+ }
+
+ @Override
+ public Class getRendererClass() {
+ return RoundedBarRenderer.class;
+ }
+
+ @Override
+ public RoundedBarRenderer doGetRendererInstance(XYPlot xyPlot) {
+ return new RoundedBarRenderer(xyPlot);
+ }
+}
+```
+
+## Create the Renderer
+All we want to do here is tweak the way `BarRenderer` behaves a little so we'll use it as our base class:
+
+```java
+class RoundedBarRenderer extends BarRenderer {
+
+ public RoundedBarRenderer(XYPlot plot) {
+ super(plot);
+ }
+
+ @Override
+ protected void drawBar(Canvas canvas, Bar bar, RectF rect) {
+ // TODO this is where we'll add our custom behavior
+ }
+}
+```
+
+In the case of our RoundedBarFormatter, the behavior we need to change all exits within the
+`drawBar(Canvas, Bar, RectF)` method. The existing implementation uses `Canvas.drawRect(...)` to
+draw bars. We could use a stroke with rounded edges, but this would cause the edges on both the top
+and the bottom to be rounded, which won't look right. We'll draw using a path instead:
+
+```java
+@Override
+protected void drawBar(Canvas canvas, Bar bar, RectF rect) {
+
+ // skip nulls:
+ if(bar.getY() == null) {
+ return;
+ }
+
+ RoundedBarFormatter formatter = getFormatter(bar.i, bar.series);
+ if(formatter == null) {
+ formatter = bar.formatter;
+ }
+
+ // don't need to draw if the bar lacks height or width:
+ if (rect.height() > 0 && rect.width() > 0) {
+ final Path path = new Path();
+ final float arcHeightPx = 20;
+ final float adjustedTop = rect.top + arcHeightPx;
+ final RectF cap = new RectF(rect.left, rect.top, rect.right,rect.top + 2 * arcHeightPx);
+
+ // start at bottom-left and move clock-wise around the shape:
+ path.moveTo(rect.left, rect.bottom);
+ path.lineTo(rect.left, adjustedTop);
+ path.arcTo(cap, 180, 180, false);
+ path.lineTo(rect.right, rect.bottom);
+ path.close();
+
+ canvas.drawPath(path, formatter.getFillPaint());
+ canvas.drawPath(path, formatter.getBorderPaint());
+ }
+}
+```
+
+## Use It
+Using the new custom renderer is as easy as adding a series to a plot using `RoundedBarFormatter`:
+
+```java
+plot.addSeries(series1, new RoundedBarFormatter(Color.RED));
+plot.addSeries(series2, new RoundedBarFormatter(Color.BLUE));
+```
+
+Here's what this would look like used in the demo app's `SimpleXYPlotActivity`:
+
+
diff --git a/docs/grouprenderer.md b/docs/grouprenderer.md
index 755b05eb..8a8ce9d1 100644
--- a/docs/grouprenderer.md
+++ b/docs/grouprenderer.md
@@ -1,11 +1,11 @@
# GroupRenderer
-GroupRenderer is a special implementation of XYSeriesRenderer that combines multiple instances
-of XYSeries into a single virtual series of s higher dimension. Examples of GroupRenderer are:
+`GroupRenderer` is a special implementation of `XYSeriesRenderer` that combines multiple instances
+of `XYSeries` into a single virtual series of s higher dimension. Examples of `GroupRenderer` are:
* BarRenderer
* CandlestickRenderer
-Let's take a quick look at CandlestickRenderer. A candlestick chart is basically a two two dimensional
+Let's take a quick look at `CandlestickRenderer`. A candlestick chart is basically a two two dimensional
representation of a complex value; for every value of x a single candlestick is drawn, but there are
four values (dimensions) that make up each candlestick:
@@ -14,5 +14,5 @@ four values (dimensions) that make up each candlestick:
* high
* low
-When a series is added to the plot with a formatter associated with a renderer that extends GroupRenderer,
+When a series is added to the plot with a formatter associated with a renderer that extends `GroupRenderer`,
all series added with formatters of the same type are automatically "grouped" together during rendering.
\ No newline at end of file
diff --git a/docs/images/screens/rounded_bar_renderer.png b/docs/images/screens/rounded_bar_renderer.png
new file mode 100644
index 00000000..4afd37d4
Binary files /dev/null and b/docs/images/screens/rounded_bar_renderer.png differ
diff --git a/docs/images/sizing/abs100x-abs100y.png b/docs/images/sizing/abs100x-abs100y.png
new file mode 100644
index 00000000..03176840
Binary files /dev/null and b/docs/images/sizing/abs100x-abs100y.png differ
diff --git a/docs/images/sizing/abs100x-abs150y.png b/docs/images/sizing/abs100x-abs150y.png
new file mode 100644
index 00000000..c1c8159d
Binary files /dev/null and b/docs/images/sizing/abs100x-abs150y.png differ
diff --git a/docs/images/sizing/abs100x-rel1y.png b/docs/images/sizing/abs100x-rel1y.png
new file mode 100644
index 00000000..b62cf708
Binary files /dev/null and b/docs/images/sizing/abs100x-rel1y.png differ
diff --git a/docs/images/sizing/fil50x-fil50y.png b/docs/images/sizing/fil50x-fil50y.png
new file mode 100644
index 00000000..6dd24012
Binary files /dev/null and b/docs/images/sizing/fil50x-fil50y.png differ
diff --git a/docs/images/sizing/rel075x-abs100y.png b/docs/images/sizing/rel075x-abs100y.png
new file mode 100644
index 00000000..d0328550
Binary files /dev/null and b/docs/images/sizing/rel075x-abs100y.png differ
diff --git a/docs/index.md b/docs/index.md
index 16d136a5..90a8a021 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -15,6 +15,7 @@ specific plot types explaining styling and other advanced topics.
* [Quickstart](quickstart.md) :star:
* [Quickstart (YouTube Video)](https://www.youtube.com/watch?v=wEFkzQY_wWI) :movie_camera:
* [Plot Composition](plot_composition.md)
+* [The Legend](legend.md)
* [XY Plots](xyplot.md)
* [Bar Charts](barchart.md)
* [Candlestick Charts](candlestick.md)
@@ -22,6 +23,7 @@ specific plot types explaining styling and other advanced topics.
* [Bubble Charts](bubblechart.md)
* [Dynamic Plots](dynamicdata.md)
* [Advanced XY Plot Topics](advanced_xy_plot.md)
+* [Custom Renderers](custom_renderer.md)
# Examples
Source code examples of the various plot types.
@@ -37,8 +39,12 @@ Source code examples of the various plot types.
* [An ECG Example](../demoapp/src/main/java/com/androidplot/demos/ECGExample.java)
* [f(x) Plot](../demoapp/src/main/java/com/androidplot/demos/FXPlotExampleActivity.java)
+# XML Attributes
+A complete list of XML attributes is [available here](attrs.md).
# Javadoc
-The latest Javadocs are [available here](https://circleci.com/api/v1/project/halfhp/androidplot/latest/artifacts/0/$CIRCLE_ARTIFACTS/javadoc/index.html).
+Javadocs for versions through 1.5.7 are [available here](https://javadoc.io/doc/com.androidplot/androidplot-core). Due to a build issue
+producing Javadocs for the latest releases has been paused. There have been very
+few changes to the API since 1.5.7 so the Javadocs should still be useful in the mean time.
# Release Notes
Full release notes are [available here](release_notes.md)
diff --git a/docs/legend.md b/docs/legend.md
new file mode 100644
index 00000000..c3ea90e4
--- /dev/null
+++ b/docs/legend.md
@@ -0,0 +1,63 @@
+# The Legend
+For `Plot` types that support it, the legend displays a list of elements in the plot along with
+a color coded icon. The color coded icon is automatically generated using the colors and line styles
+used to render the associated item. In the case of a `Series`, this is the `Formatter` you associated
+with the `Series` when you added it to your `Plot`.
+
+# Showing / Hiding the Legend
+Depending on the `Plot` type(s) you are using, the legend may or may not be visible by default. To
+can enable / disable the legend:
+
+```java
+plot.getLegend().setVisible(true|false);
+```
+
+# Hiding Series Items
+You can tell Androidplot not to generate a legend item for a `Series` by configuring it's associated
+`Formatter`:
+
+```java
+formatter.setLegendIconEnabled(false);
+```
+
+## The TableModel
+The `TableModel` controls how and where each item in the legend is drawn. Androidplot provides two
+default implementations; `DynamicTableModel` and `FixedTableModel` (detailed below). All `TableModel` implementations
+organize elements into a grid. This grid is populated with items based on the order which it's corresponding
+series was added to the plot. This ordering can be further controlled by setting the `TableModel`'s
+`TableOrder` param to either [ROW_MAJOR](https://en.wikipedia.org/wiki/Row-major_order) (items are added left-to-right, top-down)
+or `COLUMN_MAJOR` (items are added top-down, left-to-right).
+
+### DynamicTableModel
+The `DynamicTableModel` takes a desired of numbered rows and columns and evenly subdivides the `LegendWidget`'s
+visible space into cells. For example, A 2x2 legend using `ROW_MAJOR` ordering:
+
+```java
+plot.getLegend().setTableModel(new DynamicTableModel(2, 2, TableOrder.ROW_MAJOR));
+```
+
+### FixedTableModel
+The `FixedTableModel` takes a desired size of each cell in pixels and adds cells using the specified `TableOrder`.
+It automatically wraps to the next row or column (based on `TableOrder`) when the cell being added
+exceeds the legend's available space on a given axis. For example, A `FixedTableModel` using 300w*100h cells and
+a TableOrder of `COLUMN_MAJOR`:
+
+```java
+plot.getLegend().setTableModel(new FixedTableModel(PixelUtils.dpToPix(300),
+ PixelUtils.dpToPix(100), TableOrder.COLUMN_MAJOR));
+```
+
+# Sorting Legend Entries
+You can control the order of Legend entries by setting a custom `Comparator` on the legend:
+
+```java
+Comparator<...> myComparator = ...
+plot.getLegend().setLegendItemComparator(myComparator);
+```
+
+Using a custom `Comparator` in conjunction with `ROW_MAJOR` and `COLUMN_MAJOR` properties on the `TableModel`
+(show above) gives you full control over the display ordering of your legend entries.
+
+# Positioning and Resizing
+The legend is just an implementation of a Widget and is positioned and resized in the same ways
+that all Widget instances are positioned. See the [Plot Composition](plot_composition.md) doc for details.
\ No newline at end of file
diff --git a/docs/plot_composition.md b/docs/plot_composition.md
index 20b80952..2c51e349 100644
--- a/docs/plot_composition.md
+++ b/docs/plot_composition.md
@@ -1,26 +1,26 @@
# Plot Composition
-All plots in Androidplot inherit from the abstract base class Plot which provides common behaviors
-for all Plot implementations.
+All plots in Androidplot inherit from the abstract base class `Plot` which provides common behaviors
+for all `Plot` implementations.
# Widgets
-Plots are composed of one or more Widgets. A Widget is an abstraction of a visual
+Plots are composed of one or more Widgets. A `Widget` is an abstraction of a visual
component that may be positioned and scaled within the visible area of a Plot. For example,
-an XY Plot is typically composed of these 5 Widgets:
+an `XYPlot` is typically composed of these 5 `Widgets`:
* Title
* Graph
* Domain Label
* Range Label
-* Legend
+* [Legend](legend.md)
-All Plot implementations will contain at least one default Widget providing the core
-behavior encapsulated by that Plot. In addition to moving and scaling these Widgets, developers may
-also extend them and replace the Plot's default instance with the derived implenentation in order to
+All implementations of `Plot` will contain at least one default `Widget` providing the core
+behavior encapsulated by that `Plot`. In addition to moving and scaling a `Widget`, developers may
+also extend them and replace the `Plot` instance's default instance with the derived implementation in order to
get custom behavior.
# The LayoutManager
-The LayoutManager provides the logic for visually positioning and scaling Widgets; all Plot implementations
-contain an instance of LayoutManager that can be retrieved via `Plot.getLayoutManager()`.
+The `LayoutManager` provides the logic for visually positioning and scaling Widgets; all `Plot` implementations
+contain an instance of `LayoutManager` that can be retrieved via `Plot.getLayoutManager()`.
## Z-Indexing
Z-indexing is a 2D drawing concept which associates each drawable entity with a value that determines
@@ -28,12 +28,12 @@ which elements get drawn onto the screen first, producing the visual effect that
on top of others.
While Androidplot uses the term "z-index" it's implemented internally as a linked list to prevent the possibility
-of duplicate index values and therefore ensuring that the drawing order of Widgets is always explicit.
+of duplicate index values and therefore ensuring that `Widget` drawing order is always explicit.
The [Layerable](../androidplot-core/src/main/java/com/androidplot/util/Layerable.java) interface
-defines methods used for manipulating the z-index of a Widget.
+defines methods used for manipulating the z-index of a `Widget`.
## Adding & Removing Widgets
-New Widgets can be added either to the front or back of the z-index using these methods:
+New `Widget` instances can be added either to the front or back of the z-index using these methods:
* `LayoutManager.addToTop(Widget)`
* `LayoutManager.addToBottom(Widget)`
@@ -76,6 +76,9 @@ Using example #1 above, an Anchor value of `Anchor.LEFT_TOP` means that the top
would be positioned at the screen coordinate [200, 50].
#### Examples
+The examples below illustrate positioning an `XYGraphWidget` of an `XYPlot`.
+
+xml:
```xml
ap:graphAnchor="right_bottom"
ap:graphHorizontalPositioning="absolute_from_right"
@@ -83,8 +86,16 @@ ap:graphHorizontalPosition="0dp"
ap:graphVerticalPositioning="relative_from_bottom"
ap:graphVerticalPosition="0dp"
```
+java:
+```java
+plot.getGraph().position(
+ 0, HorizontalPositioning.ABSOLUTE_FROM_RIGHT,
+ 0, VerticalPositioning.RELATIVE_TO_BOTTOM);
+```

***
+
+xml:
```xml
ap:graphAnchor="right_bottom"
ap:graphHorizontalPositioning="absolute_from_right"
@@ -92,8 +103,16 @@ ap:graphHorizontalPosition="10dp"
ap:graphVerticalPositioning="relative_from_bottom"
ap:graphVerticalPosition="10dp"
```
+java:
+```java
+plot.getGraph().position(
+ PixelUtils.dpToPix(10), HorizontalPositioning.ABSOLUTE_FROM_RIGHT,
+ PixelUtils.dpToPix(10), VerticalPositioning.RELATIVE_TO_BOTTOM);
+```

***
+
+xml:
```xml
ap:graphAnchor="right_bottom"
ap:graphHorizontalPositioning="absolute_from_left"
@@ -101,8 +120,16 @@ ap:graphHorizontalPosition="0dp"
ap:graphVerticalPositioning="relative_from_top"
ap:graphVerticalPosition="0dp"
```
-
+java:
+```java
+plot.getGraph().position(
+ 0, HorizontalPositioning.ABSOLUTE_FROM_LEFT,
+ 0, VerticalPositioning.RELATIVE_TO_TOP);
+```
+
***
+
+xml:
```xml
ap:graphAnchor="center"
ap:graphHorizontalPositioning="absolute_from_center"
@@ -110,23 +137,117 @@ ap:graphHorizontalPosition="0dp"
ap:graphVerticalPositioning="absolute_from_center"
ap:graphVerticalPosition="0dp"
```
+java:
+```java
+plot.getGraph().position(
+ 0, HorizontalPositioning.ABSOLUTE_FROM_CENTER,
+ 0, VerticalPositioning.ABSOLUTE_FROM_CENTER);
+```

-## Scaling Widgets
-TODO
+## Sizing Widgets
+The size and shape of a `Widget` is controlled by it's `setSize(Size)` method.
+
+### Size
+The `Size` parameter of `Widget.setSize(Size)` defines the height and width of the associated Widget.
+It is composed of two `SizeMetric` instances; one for height and one for width.
+`Size` provides two constructors: `Size(SizeMetric, SizeMetric)` or
+`Size(int, SizeMode, int, SizeMode)`. Constructor params represent height and width respectively.
### SizeMetric
-TODO
+A `SizeMetric` is composed of a `SizeMode` and a float value. There are three kinds of `SizeMode`:
+
+* ABSOLUTE - float value defines the size metric as an absolute value in pixels.
+* RELATIVE - float value defines the size of the metric relative to the size of the containing Plot along
+the associated axis, in the range of 0.0 to 1.0.
+* FILL - float value defines an absolute value in pixels to subtract from the size of the containing
+Plot along the associated axis and the SizeMetric "fills" the difference.
#### Examples
-TODO
+The examples below illustrate positioning an `XYGraphWidget` of an `XYPlot` and
+assume centered positioning is applied as described in the [Positioning Widgets](#positioning-widgets) section above.
+
+xml:
+```xml
+ap:graphHeightMode="absolute"
+ap:graphHeight="100dp"
+ap:graphWidthMode="absolute"
+ap:graphWidth="100dp"
+```
+java:
+```java
+plot.getGraph().setSize(new Size(
+ PixelUtils.dpToPix(100), SizeMode.ABSOLUTE,
+ PixelUtils.dpToPix(100), SizeMode.ABSOLUTE));
+```
+
+***
+xml:
+```xml
+ap:graphHeightMode="absolute"
+ap:graphHeight="150dp"
+ap:graphWidthMode="absolute"
+ap:graphWidth="100dp"
+```
+java:
+```java
+plot.getGraph().setSize(new Size(
+ PixelUtils.dpToPix(150), SizeMode.ABSOLUTE,
+ PixelUtils.dpToPix(100), SizeMode.ABSOLUTE));
+```
+
+***
+xml:
+```xml
+ap:graphHeightMode="relative"
+ap:graphHeight="1.0"
+ap:graphWidthMode="absolute"
+ap:graphWidth="100dp"
+```
+java:
+```java
+plot.getGraph().setSize(new Size(
+ 1.0f, SizeMode.RELATIVE,
+ PixelUtils.dpToPix(100), SizeMode.ABSOLUTE));
+```
+
+***
+xml:
+```xml
+ap:graphHeightMode="absolute"
+ap:graphHeight="100dp"
+ap:graphWidthMode="relative"
+ap:graphWidth="0.75"
+```
+java:
+```java
+plot.getGraph().setSize(new Size(
+ PixelUtils.dpToPix(100), SizeMode.ABSOLUTE,
+ 0.75f, SizeMode.RELATIVE));
+```
+
+***
+xml:
+```xml
+ap:graphHeightMode="fill"
+ap:graphHeight="50dp"
+ap:graphWidthMode="fill"
+ap:graphWidth="50dp"
+```
+java:
+```java
+plot.getGraph().setSize(new Size(
+ PixelUtils.dpToPix(50), SizeMode.FILL,
+ PixelUtils.dpToPix(50), SizeMode.FILL));
+```
+
## Margins and Padding
Every widget has a margin, padding and an optional border that can be drawn around it. These params behave
very similarly to those defined in the [CSS Box Model](http://www.w3schools.com/css/css_boxmodel.asp).
-## Markup Mode.
+## Markup Mode
If you're having trouble visualizing the effects of tweaking margins and padding, you can enable
markup mode which will highlight these spaces on each widget, as well as draw a green line around it's
absolute border.
@@ -160,7 +281,7 @@ text style, etc. to apply while drawing.
## Renderers
The Renderer is what renders Series data onto a Plot. Users can provide their own custom rendering behavior
by writing their own Renderer implementation along with a custom Formatter telling Androidplot about the
-Renderer via the `Formatter.getRendererClass()` method.
+Renderer via the `Formatter.getRendererClass()` method. See [Custom Renderer](custom_rnderer.md) documentation.
# XML Styling
Androidplot supports an increasing number of XML attributes. The two best resources for learning about
diff --git a/docs/quickstart.md b/docs/quickstart.md
index 57bd090c..3bd87c3d 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 {
- compile "com.androidplot:androidplot-core:1.4.2"
+ implementation "com.androidplot:androidplot-core:1.5.11"
}
```
+*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:
@@ -44,8 +47,8 @@ and add an XYPlot view:
ap:lineLabelRotationBottom="-45"/>
```
-This example uses a default style to decorate the plot. The full list of styleable attributes is
-[available here](../androidplot-core/src/main/res/values/attrs.xml). While new attributes are added regularly,
+This example uses a default style to decorate the plot. The full list of XML styleable attributes is
+[available here](attrs.md). While new attributes are added regularly,
not all configurable properties are yet available.
If something you need is missing, use [Fig Syntax](https://github.com/halfhp/fig)
@@ -55,6 +58,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 +180,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 +201,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
diff --git a/docs/release_notes.md b/docs/release_notes.md
index 995cfbbc..50d52c11 100644
--- a/docs/release_notes.md
+++ b/docs/release_notes.md
@@ -2,13 +2,89 @@
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.11
+* Update project to latest gradle / build tools
+* Fix issue with jetifier flagging an outdated dependency
+
+# 1.5.10
+* Update project to use latest gradle / build tools
+* (#114) fix `setLinesPerRangeLabel` & `setLinesPerDomainLabel`
+
+# 1.5.9
+* (#107) Fix ambiguous ordinal for render mode attributes
+* (#104) Fix issue with background rendering in RecyclerView.
+* Adds a RecyclerView example to demo app.
+
+# 1.5.8
+* Maintenance release - update dependences, get off jcenter, etc.
+
+# 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.
+
+# 1.5.5
+
+* (#76) Fixed a bug that could cause a deadlock when grid steps are much larger than actual plot range.
+* (#78) Fixed a bug where setting insets on XYGraphWidget would have no effect after the plot was drawn.
+* XYGraphWidget.drawMarkerText is now marked `protected`.
+
+# 1.5.4
+
+* (#69) Fixed a bug in `SimpleXYPlot` preventing the resizing of `Y_VALS_ONLY` formatted series.
+* (#73) Fixed a bug where dynamically resizing a Plot (by marking a sibling view as `GONE`, etc.) would not resize the graph widget.
+
+# 1.5.3
+
+* Minor cleanup of Widget example source.
+* (#67) Fixed Javadoc link
+
+# 1.5.2
+
+_This version is pickier than it's predecessors about proper XML configuration. Where
+previous versions would silently ignore illegal XML attrs, a `RuntimeException` will be thrown._
+* Added [sizing documentation](plot_composition.md#sizing-widgets)
+* Added [custom renderer documentation](custom_renderer.md)
+* Fixed (#61) Bug in `XYGraphWidget.screenToSeriesY(...)`.
+* Fixed (#63) Fixed compatibility issue with Gradle 3.x.x that caused issues with XML parsing.
+
+# 1.5.1
+
+* (#52) Fixed minor NPE issue
+* (#55) Fixed bug with `PieRenderer.getContainingSegment` not working for very large segments.
+
+# 1.5.0
+
+_Updates to legend functionality in this version may result in changes to the display order
+of legend items in some cases. A custom `Comparator` can be used to resolve this if necessary;
+see the [legend doc](legend.md) for implementation details._
+
+* Added [legend doc](legend.md)
+* Added legend support to `PieChart`
+* Added configurable legend item sorting
+* (#45) Auto range boundaries calculation fix for when using a fixed domain range and a `FastXYSeries`
+* Minor Performance Optimizations
+
+# 1.4.3
+
+* (#39) `FastLineAndPointRenderer` now renders vertices for legend items.
+* Added [XML Attrs reference doc](attrs.md).
+
# 1.4.2
+
* (#32) New step mode: `INCREMENT_BY_FIT`.
-* (#33) PanZoom support for 'INCREMENT_BY_FIT'.
+* (#33) `PanZoom` support for `INCREMENT_BY_FIT`.
* (#34) Removed examples and documentation for serializing `SeriesRegistry` to preserve state.
# 1.4.1
-* (#26) Fixed an NPE issue when drawing null values with a PointLabeler.
+
+* (#26) Fixed an NPE issue when drawing null values with a `PointLabeler`.
* Fixed a broken link in Quickstart doc.
# 1.4.0
@@ -88,6 +164,7 @@ See the [pie chart documentation](piechart.md) for usage details.
* Removed InteractiveXYPlot as PanZoom makes it obsolete.
# 1.0.0
+
This is a factor of several core elements of the Androidplot lib. The general theme was to
make class and method names more intuitive and to make xml styling more powerful.
diff --git a/docs/xyplot.md b/docs/xyplot.md
index 1e363d12..ce7e1526 100644
--- a/docs/xyplot.md
+++ b/docs/xyplot.md
@@ -87,14 +87,14 @@ These are the horizontal lines drawn on a graph. These lines are configured via
Androidplot provides these step modes:
### Subdivide
-When using `BoundaryMode.SUBDIVIDE`, the graph is subdivided into the specified number of sections.
+When using `StepMode.SUBDIVIDE`, the graph is subdivided into the specified number of sections.
### IncrementByValue
-`BoundaryMode.INCREMENT_BY_VALUE` instructs Androidplot draw grid lines at the specified interval. This
+`StepMode.INCREMENT_BY_VALUE` instructs Androidplot draw grid lines at the specified interval. This
is the most commonly used modes as is produces an easy to read result.
### IncrementByPixels
-`BoundaryMode.INCREMENT_BY_PIXELS` behaves identically to `BoundaryMode.INCREMENT_BY_VALUE` except that
+`StepMode.INCREMENT_BY_PIXELS` behaves identically to `StepMode.INCREMENT_BY_VALUE` except that
the increment quantity is expressed in pixels.
## Domain & Range Labels
@@ -217,6 +217,25 @@ then the plot will pan and zoom on both axes infinitely. To set the plot's oute
plot.getOuterLimits().set(0, 100, 0, 100);
```
+# Saving & Restoring PanZoom State
+The `PanZoom` class provides convenience methods for saving and restoring state from your `Activity`:
+
+```java
+// save the current pan/zoom state
+@Override
+public void onSaveInstanceState(Bundle bundle) {
+ bundle.putSerializable("pan-zoom-state", panZoom.getState());
+}
+
+// restore the previously saved pan/zoom state
+@Override
+public void onRestoreInstanceState(Bundle bundle) {
+ PanZoom.State state = (PanZoom.State) bundle.getSerializable("pan-zoom-state");
+ panZoom.setState(state);
+ plot.redraw();
+}
+```
+
For a more detailed look at pan & zoom behavior, check out the [Touch Zoom Example source code](../demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java).
# Series Renderers
@@ -292,44 +311,9 @@ See the [candlestick documentation](candlestick.md)
Smooth lines can be created by applying the
[Catmull-Rom interpolator](http://androidplot.com/smooth-curves-and-androidplot/) to your series' Format.
-# The Legend
-By default, Androidplot will automatically produce a legend for your Plot. You however choose to hide the legend
-or you can customize it to suit your needs.
-
-# Hiding Legend Items
-As mentioned above, Androidplot automatically produces a legend for your Plot. This "auto legend" includes
-items for each series added to the plot. If you wish to omit a series from the legend:
-
-```java
-formatter.setLegendIconEnabled(false);
-```
-
-## The TableModel
-The `TableModel` controls how and where each item in the legend is drawn. Androidplot provides two
-default implementations; `DynamicTableModel` and `FixedTableModel` (detailed below). All `TableModel` implementations
-organize elements into a grid. This grid is populated with items based on the order which it's corresponding
-series was added to the plot. This ordering can be further controlled by setting the `TableModel`'s
-`TableOrder` param to either [ROW_MAJOR](https://en.wikipedia.org/wiki/Row-major_order) (items are added left-to-right, top-down)
-or `COLUMN_MAJOR` (items are added top-down, left-to-right).
-
-### DynamicTableModel
-The `DynamicTableModel` takes a desired of numbered rows and columns and evenly subdivides the `LegendWidget`'s
-visible space into cells. For example, A 2x2 legend using `ROW_MAJOR` ordering:
-
-```java
-plot.getLegend().setTableModel(new DynamicTableModel(2, 2, TableOrder.ROW_MAJOR));
-```
-
-### FixedTableModel
-The `FixedTableModel` takes a desired size of each cell in pixels and adds cells using the specified `TableOrder`.
-It automatically wraps to the next row or column (based on `TableOrder`) when the cell being added
-exceeds the legend's available space on a given axis. For example, A `FixedTableModel` using 300w*100h cells and
-a TableOrder of `COLUMN_MAJOR`:
-
-```java
-plot.getLegend().setTableModel(new FixedTableModel(PixelUtils.dpToPix(300),
- PixelUtils.dpToPix(100), TableOrder.COLUMN_MAJOR));
-```
+# The Legend
+By default, Androidplot will automatically produce a legend for your `XYPlot`. See [the legend](legend.md) doc
+for usage details.
# Graph Rotation
Androidplot provides the `Widget.setRotation(Widget.Rotation)` method for controlling the orientation
diff --git a/gradle.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 0429c501..e9defc20 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Thu Mar 23 07:48:11 CDT 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-3.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
diff --git a/misc/download_keystore.sh b/misc/download_keystore.sh
deleted file mode 100755
index 99fd503a..00000000
--- a/misc/download_keystore.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-# use curl to download a keystore from $KEYSTORE_URI, if set,
-# to the path/filename set in $KEYSTORE.
-if [[ $KEYSTORE && ${KEYSTORE} && $KEYSTORE_URI && ${KEYSTORE_URI} ]]
-then
- echo "Keystore detected - downloading..."
- curl -L -o ${KEYSTORE} ${KEYSTORE_URI}
-else
- echo "Keystore uri not set. .APK artifact will not be signed."
-fi
\ No newline at end of file