diff --git a/.gitignore b/.gitignore index 916a2c0cc..77cbeee5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,45 @@ +_Store + + +# Generated by http://gitignore.io + +### Eclipse ### +*.pydevproject +.project +.metadata +bin/** +tmp/** +tmp/**/* +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + +### IntelliJ ### +*.iml +*.ipr +*.iws +.idea/ + +### Android ### # built application files +*.apk *.ap_ # files for the dex VM @@ -10,7 +51,6 @@ # generated files bin/ gen/ -lint.xml # Local configuration file (sdk path, etc) local.properties @@ -18,29 +58,42 @@ local.properties # Eclipse project files .classpath .project -.settings -.checkstyle -# Maven -target -release.properties -pom.xml.* +# Proguard folder generated by Eclipse +proguard/ -# Ant -build.xml -ant.properties -local.properties -proguard.cfg -proguard-project.txt +# Proguard folder generated by Intellij +proguard_logs/ # Intellij project files *.iml *.ipr *.iws .idea/ -out/ -gen-external-apklibs/ -# Gradle -.gradle -build \ No newline at end of file +adt-bundle-windows-x86_64/ + +### Windows ### +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +### Linux ### +.* +!.gitignore +*~ +# Generated by http://gitignore.io + +### Gradle ### +# Exclude Folder List # +.gradle/ +build/ +.gradletasknamecache +gradle.properties + diff --git a/GVBar.png b/GVBar.png deleted file mode 100644 index 7eabad12c..000000000 Binary files a/GVBar.png and /dev/null differ diff --git a/GVLine.jpg b/GVLine.jpg deleted file mode 100644 index cb93a390a..000000000 Binary files a/GVLine.jpg and /dev/null differ diff --git a/README.markdown b/README.markdown index 0215b7c1d..7732c6a8b 100644 --- a/README.markdown +++ b/README.markdown @@ -1,50 +1,86 @@ -Chart and Graph Library for Android -==================================== +# Chart and Graph Library for Android -

What is GraphView

-GraphView is a library for Android to programmatically create flexible and nice-looking diagramms. It is easy to understand, to integrate and to customize it. -At the moment there are two different types: - +## Project maintainer wanted! For time reasons I can not continue to maintain GraphView. Contact me if you are interested and serious about this project. g.jjoe64@gmail.com -Tested on Android 1.6, 2.2, 2.3 and 3.0 (honeycomb, tablet), 4.0. +## What is GraphView - - - +GraphView is a library for Android to programmatically create +flexible and nice-looking diagrams. +It is **easy** to understand, to integrate and to customize. -

Features

+Supported graph types: +* Line Graphs +* Bar Graphs +* Point Graphs +* or implement your own custom types. -* Two chart types -Line Chart and Bar Chart. + + + + + + +## Top Features + +* Line Chart, Bar Chart, Points +* Combination of different graph types +* Scrolling vertical and horizontal +. You can scroll with a finger touch move gesture. +* Scaling / Zooming vertical and horizontal +. With two-fingers touch scale gesture (Multi-touch), the viewport can be changed. +* Realtime Graph (Live change of data) +* Second scale axis * Draw multiple series of data -Let the diagram show more that one series in a graph. You can set a color and a description for every series. +. Let the diagram show more that one series in a graph. You can set a color and a description for every series. * Show legend -A legend can be displayed inline the chart. You can set the width and the vertical align (top, middle, bottom). +. A legend can be displayed inline the chart. You can set the width and the vertical align (top, middle, bottom). * Custom labels -The labels for the x- and y-axis are generated automatically. But you can set your own labels, Strings are possible. +. The labels for the x- and y-axis are generated automatically. But you can set your own labels, Strings are possible. * Handle incomplete data -It's possible to give the data in different frequency. +. It's possible to give the data in different frequency. * Viewport -You can limit the viewport so that only a part of the data will be displayed. -* Scrolling -You can scroll with a finger touch move gesture. -* Scaling / Zooming -Since Android 2.3! With two-fingers touch scale gesture (Multi-touch), the viewport can be changed. -* Background (line graph) -Optionally draws a light background under the diagram stroke. +. You can limit the viewport so that only a part of the data will be displayed. * Manual Y axis limits -* Realtime Graph (Live) -* And more +* And much more... Check out the project page and/or the demo app + +## How to use + +1) Add gradle dependency: +``` +implementation 'com.jjoe64:graphview:4.2.2' +``` + +2) Add view to layout: +```xml + +``` -

How to use

-View GraphView page
http://android-graphview.org
+3) Add some data: +```java +GraphView graph = (GraphView) findViewById(R.id.graph); +LineGraphSeries series = new LineGraphSeries(new DataPoint[] { + new DataPoint(0, 1), + new DataPoint(1, 5), + new DataPoint(2, 3), + new DataPoint(3, 2), + new DataPoint(4, 6) +}); +graph.addSeries(series); +``` + +## Download Demo project at Google Play Store + +
+Showcase GraphView Demo App + +## More examples and documentation + +Get started at project wiki homepage -

Important

To show you how to integrate the library into an existing project see the GraphView-Demos project! See GraphView-Demos for examples. https://github.com/jjoe64/GraphView-Demos
-
View GraphView page http://android-graphview.org - +View GraphView wiki page https://github.com/jjoe64/GraphView/wiki diff --git a/README.new-version.md b/README.new-version.md new file mode 100644 index 000000000..8bd815f25 --- /dev/null +++ b/README.new-version.md @@ -0,0 +1,59 @@ +How to create a new version for maven repo +-------------------------------------------- +create sources.jar +- $ jar cvf sources.jar src + +create java doc jar +- $ mkdir javadoc +- $ javadoc -d javadoc -sourcepath src/main/java/ -subpackages com.jjoe64 +- $ jar cvf javadoc.jar javadoc + +change version in gradle.properties + +uncomment part for publishing in build.gradle + +(once) create a gpg file +- gpg --gen-key + +(once) publish key +- gpg --send-keys D8C3B041 +and/or here as ascii +- gpg --export -a D8C3B041 +- http://keyserver.ubuntu.com:11371/ + +=> needs some time + +hardcode gpg key password in maven_push.gradle + +hardcode user/pwd of nexus account in maven_push.gradle + +success gradle task uploadArchives +- ./gradlew --rerun-tasks uploadArchives +- enter gpg info (id:D8C3B041 / path: /Users/jonas/.gnupg/secring.gpg / PWD) + +open https://oss.sonatype.org + +login + +Staging Repositiories + +search: jjoe64 + +Close entry + +Refresh/Wait + +Release entry + +Wait some days + +## update java doc + +$ javadoc -d javadoc -sourcepath src/main/java/ -subpackages com.jjoe64 +$ mv javadoc/ .. +$ git checkout gh-pages +$ rm -rf javadoc +$ mv ../javadoc/ . +$ git add javadoc +$ git commit -a + diff --git a/anim.gif b/anim.gif new file mode 100644 index 000000000..43e2d90f7 Binary files /dev/null and b/anim.gif differ diff --git a/build.gradle b/build.gradle index 8b86123cc..ddd7534ac 100644 --- a/build.gradle +++ b/build.gradle @@ -1,24 +1,67 @@ buildscript { repositories { mavenCentral() + google() + jcenter() } - dependencies { - classpath 'com.android.tools.build:gradle:0.6.+' + classpath 'com.android.tools.build:gradle:3.5.3' } } -apply plugin: 'android-library' +wrapper { + gradleVersion = '5.6' +} + +apply plugin: 'com.android.library' + android { - compileSdkVersion 17 - buildToolsVersion '17.0.0' - - sourceSets { - main { - manifest.srcFile 'AndroidManifest.xml' - java.srcDirs = ['src'] - res.srcDirs = ['res'] + compileSdkVersion 27 + buildToolsVersion '28.0.3' + + defaultConfig { + minSdkVersion 9 + targetSdkVersion 27 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false } } + lintOptions { + abortOnError false + } + +} + +dependencies { + implementation 'androidx.core:core:1.0.0-beta01' +} + + +//this is used to generate .jar files and push to maven repo +// This is the actual solution, as in http://stackoverflow.com/a/19037807/1002054 +task clearJar(type: Delete) { + delete 'build/outputs/myCompiledLibrary.jar' +} + +task makeJar(type: Copy) { + from('build/intermediates/bundles/release/') + into('build/outputs/') + include('classes.jar') + rename ('classes.jar', 'myCompiledLibrary.jar') +} + +makeJar.dependsOn(clearJar, build) + + +apply from: './maven_push.gradle' + +repositories { + google() + mavenCentral() + jcenter() } \ No newline at end of file diff --git a/doc-assets/1059439_1.png b/doc-assets/1059439_1.png new file mode 100644 index 000000000..8be6dde8b Binary files /dev/null and b/doc-assets/1059439_1.png differ diff --git a/doc-assets/4000611_1.png b/doc-assets/4000611_1.png new file mode 100644 index 000000000..a373f0487 Binary files /dev/null and b/doc-assets/4000611_1.png differ diff --git a/doc-assets/469160_orig_1.png b/doc-assets/469160_orig_1.png new file mode 100644 index 000000000..7b96d263a Binary files /dev/null and b/doc-assets/469160_orig_1.png differ diff --git a/doc-assets/5901645_1.png b/doc-assets/5901645_1.png new file mode 100644 index 000000000..962804de9 Binary files /dev/null and b/doc-assets/5901645_1.png differ diff --git a/doc-assets/6316193_orig_1.png b/doc-assets/6316193_orig_1.png new file mode 100644 index 000000000..f4f6e462a Binary files /dev/null and b/doc-assets/6316193_orig_1.png differ diff --git a/doc-assets/9303658_1.png b/doc-assets/9303658_1.png new file mode 100644 index 000000000..bbba36477 Binary files /dev/null and b/doc-assets/9303658_1.png differ diff --git a/doc-assets/Screen_Shot_2016_10_08_at_12_19_56_1.png b/doc-assets/Screen_Shot_2016_10_08_at_12_19_56_1.png new file mode 100644 index 000000000..b5d610643 Binary files /dev/null and b/doc-assets/Screen_Shot_2016_10_08_at_12_19_56_1.png differ diff --git a/doc-assets/Screen_Shot_2016_10_08_at_12_20_56_1.png b/doc-assets/Screen_Shot_2016_10_08_at_12_20_56_1.png new file mode 100644 index 000000000..77780bb31 Binary files /dev/null and b/doc-assets/Screen_Shot_2016_10_08_at_12_20_56_1.png differ diff --git a/doc-assets/Screen_Shot_2016_10_08_at_12_23_38_1.png b/doc-assets/Screen_Shot_2016_10_08_at_12_23_38_1.png new file mode 100644 index 000000000..745fc291c Binary files /dev/null and b/doc-assets/Screen_Shot_2016_10_08_at_12_23_38_1.png differ diff --git a/doc-assets/Screen_Shot_2016_10_08_at_12_24_19_1.png b/doc-assets/Screen_Shot_2016_10_08_at_12_24_19_1.png new file mode 100644 index 000000000..6f4433146 Binary files /dev/null and b/doc-assets/Screen_Shot_2016_10_08_at_12_24_19_1.png differ diff --git a/doc-assets/Screenshot_20161008_122642_1_1.png b/doc-assets/Screenshot_20161008_122642_1_1.png new file mode 100644 index 000000000..8aba95d2f Binary files /dev/null and b/doc-assets/Screenshot_20161008_122642_1_1.png differ diff --git a/doc-assets/Screenshot_20161011_210215_1.png b/doc-assets/Screenshot_20161011_210215_1.png new file mode 100644 index 000000000..0ed502672 Binary files /dev/null and b/doc-assets/Screenshot_20161011_210215_1.png differ diff --git a/doc-assets/Screenshot_20161012_180242_1.png b/doc-assets/Screenshot_20161012_180242_1.png new file mode 100644 index 000000000..acb238f26 Binary files /dev/null and b/doc-assets/Screenshot_20161012_180242_1.png differ diff --git a/doc-assets/Screenshot_20161012_180257_1.png b/doc-assets/Screenshot_20161012_180257_1.png new file mode 100644 index 000000000..dca262fe7 Binary files /dev/null and b/doc-assets/Screenshot_20161012_180257_1.png differ diff --git a/doc-assets/Screenshot_20161012_180325_1.png b/doc-assets/Screenshot_20161012_180325_1.png new file mode 100644 index 000000000..f20b1436c Binary files /dev/null and b/doc-assets/Screenshot_20161012_180325_1.png differ diff --git a/doc-assets/Screenshot_20161012_180336_1.png b/doc-assets/Screenshot_20161012_180336_1.png new file mode 100644 index 000000000..ae45497de Binary files /dev/null and b/doc-assets/Screenshot_20161012_180336_1.png differ diff --git a/doc-assets/Screenshot_20161012_180355_1.png b/doc-assets/Screenshot_20161012_180355_1.png new file mode 100644 index 000000000..f9797826c Binary files /dev/null and b/doc-assets/Screenshot_20161012_180355_1.png differ diff --git a/doc-assets/Screenshot_20161012_180404_1.png b/doc-assets/Screenshot_20161012_180404_1.png new file mode 100644 index 000000000..069978e9d Binary files /dev/null and b/doc-assets/Screenshot_20161012_180404_1.png differ diff --git a/doc-assets/snapshotshare_1.png b/doc-assets/snapshotshare_1.png new file mode 100644 index 000000000..82901109b Binary files /dev/null and b/doc-assets/snapshotshare_1.png differ diff --git a/doc/allclasses-frame.html b/doc/allclasses-frame.html deleted file mode 100644 index 999a3111e..000000000 --- a/doc/allclasses-frame.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - -All Classes - - - - - - - - - - - -All Classes -
- - - - - -
GraphView -
-GraphView.GraphViewData -
-
- - - diff --git a/doc/allclasses-noframe.html b/doc/allclasses-noframe.html deleted file mode 100644 index 82a2eb36d..000000000 --- a/doc/allclasses-noframe.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - -All Classes - - - - - - - - - - - -All Classes -
- - - - - -
GraphView -
-GraphView.GraphViewData -
-
- - - diff --git a/doc/com/jjoe64/graphview/BarGraphView.html b/doc/com/jjoe64/graphview/BarGraphView.html deleted file mode 100644 index 53e538112..000000000 --- a/doc/com/jjoe64/graphview/BarGraphView.html +++ /dev/null @@ -1,349 +0,0 @@ - - - - - - -BarGraphView - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- -

- -com.jjoe64.graphview -
-Class BarGraphView

-
-java.lang.Object
-  extended by LinearLayout
-      extended by com.jjoe64.graphview.GraphView
-          extended by com.jjoe64.graphview.BarGraphView
-
-
-
-
public class BarGraphView
extends GraphView
- - -

-Draws a Bar Chart -

- -

-

-
Author:
-
Muhammad Shahab Hameed
-
-
- -

- - - - - - - -
-Nested Class Summary
- - - - - - - -
Nested classes/interfaces inherited from class com.jjoe64.graphview.GraphView
GraphView.GraphViewData, GraphView.LegendAlign
-  - - - - - - - -
-Field Summary
- - - - - - - -
Fields inherited from class com.jjoe64.graphview.GraphView
paint
-  - - - - - - - - - - - - - -
-Constructor Summary
BarGraphView(Context context, - AttributeSet attrs) - -
-           
BarGraphView(Context context, - java.lang.String title) - -
-           
-  - - - - - - - - - - - -
-Method Summary
- voiddrawSeries(Canvas canvas, - GraphViewDataInterface[] values, - float graphwidth, - float graphheight, - float border, - double minX, - double minY, - double diffX, - double diffY, - float horstart, - GraphViewSeries.GraphViewSeriesStyle style) - -
-           
- - - - - - - -
Methods inherited from class com.jjoe64.graphview.GraphView
addSeries, drawLegend, formatLabel, getCustomLabelFormatter, getGraphViewStyle, getLegendAlign, getLegendWidth, getMaxX, getMaxY, getMinX, getMinY, isDisableTouch, isScrollable, isShowLegend, redrawAll, removeAllSeries, removeSeries, removeSeries, scrollToEnd, setCustomLabelFormatter, setDisableTouch, setGraphViewStyle, setHorizontalLabels, setLegendAlign, setLegendWidth, setManualYAxis, setManualYAxisBounds, setScalable, setScrollable, setShowLegend, setTitle, setVerticalLabels, setViewPort
- - - - - - - -
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
-  -

- - - - - - - - -
-Constructor Detail
- -

-BarGraphView

-
-public BarGraphView(Context context,
-                    AttributeSet attrs)
-
-
-
- -

-BarGraphView

-
-public BarGraphView(Context context,
-                    java.lang.String title)
-
-
- - - - - - - - -
-Method Detail
- -

-drawSeries

-
-public void drawSeries(Canvas canvas,
-                       GraphViewDataInterface[] values,
-                       float graphwidth,
-                       float graphheight,
-                       float border,
-                       double minX,
-                       double minY,
-                       double diffX,
-                       double diffY,
-                       float horstart,
-                       GraphViewSeries.GraphViewSeriesStyle style)
-
-
-
Specified by:
drawSeries in class GraphView
-
-
-
-
-
- -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/com/jjoe64/graphview/CustomLabelFormatter.html b/doc/com/jjoe64/graphview/CustomLabelFormatter.html deleted file mode 100644 index 1b441c07b..000000000 --- a/doc/com/jjoe64/graphview/CustomLabelFormatter.html +++ /dev/null @@ -1,233 +0,0 @@ - - - - - - -CustomLabelFormatter - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- -

- -com.jjoe64.graphview -
-Interface CustomLabelFormatter

-
-
-
public interface CustomLabelFormatter
- - -

-if you want to show different labels, - you can use this label formatter. - As Input you get the raw value (x or y) and - you return a String that will be displayed. - graphView.setCustomLabelFormatter(new CustomLabelFormatter() { - public String formatLabel(double value, boolean isValueX) { - if (isValueX) { - if (value < 5) { - return "small"; - } else if (value < 15) { - return "middle"; - } else { - return "big"; - } - } - return null; // let graphview generate Y-axis label for us - } - }); - -

- -

-


- -

- - - - - - - - - - - - -
-Method Summary
- java.lang.StringformatLabel(double value, - boolean isValueX) - -
-          will be called when the labels were generated
-  -

- - - - - - - - -
-Method Detail
- -

-formatLabel

-
-java.lang.String formatLabel(double value,
-                             boolean isValueX)
-
-
will be called when the labels were generated -

-

-
Parameters:
value - the raw input value (x or y)
isValueX - true if value is a x-value, false if otherwise -
Returns:
the string that will be displayed. return null if you want graphview to generate the label for you.
-
-
- -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/com/jjoe64/graphview/GraphView.GraphViewData.html b/doc/com/jjoe64/graphview/GraphView.GraphViewData.html deleted file mode 100644 index b285a7eab..000000000 --- a/doc/com/jjoe64/graphview/GraphView.GraphViewData.html +++ /dev/null @@ -1,343 +0,0 @@ - - - - - - -GraphView.GraphViewData - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- -

- -com.jjoe64.graphview -
-Class GraphView.GraphViewData

-
-java.lang.Object
-  extended by com.jjoe64.graphview.GraphView.GraphViewData
-
-
-
All Implemented Interfaces:
GraphViewDataInterface
-
-
-
Enclosing class:
GraphView
-
-
-
-
public static class GraphView.GraphViewData
extends java.lang.Object
implements GraphViewDataInterface
- - -

-one data set for a graph series -

- -

-


- -

- - - - - - - - - - - - - - - -
-Field Summary
- doublevalueX - -
-           
- doublevalueY - -
-           
-  - - - - - - - - - - -
-Constructor Summary
GraphView.GraphViewData(double valueX, - double valueY) - -
-           
-  - - - - - - - - - - - - - - - -
-Method Summary
- doublegetX() - -
-           
- doublegetY() - -
-           
- - - - - - - -
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
-  -

- - - - - - - - -
-Field Detail
- -

-valueX

-
-public final double valueX
-
-
-
-
-
- -

-valueY

-
-public final double valueY
-
-
-
-
- - - - - - - - -
-Constructor Detail
- -

-GraphView.GraphViewData

-
-public GraphView.GraphViewData(double valueX,
-                               double valueY)
-
-
- - - - - - - - -
-Method Detail
- -

-getX

-
-public double getX()
-
-
-
Specified by:
getX in interface GraphViewDataInterface
-
-
-
-
-
-
- -

-getY

-
-public double getY()
-
-
-
Specified by:
getY in interface GraphViewDataInterface
-
-
-
-
-
- -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/com/jjoe64/graphview/GraphView.LegendAlign.html b/doc/com/jjoe64/graphview/GraphView.LegendAlign.html deleted file mode 100644 index 52e284555..000000000 --- a/doc/com/jjoe64/graphview/GraphView.LegendAlign.html +++ /dev/null @@ -1,341 +0,0 @@ - - - - - - -GraphView.LegendAlign - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- -

- -com.jjoe64.graphview -
-Enum GraphView.LegendAlign

-
-java.lang.Object
-  extended by java.lang.Enum<GraphView.LegendAlign>
-      extended by com.jjoe64.graphview.GraphView.LegendAlign
-
-
-
All Implemented Interfaces:
java.io.Serializable, java.lang.Comparable<GraphView.LegendAlign>
-
-
-
Enclosing class:
GraphView
-
-
-
-
public static enum GraphView.LegendAlign
extends java.lang.Enum<GraphView.LegendAlign>
- - -

-


- -

- - - - - - - - - - - - - - - - -
-Enum Constant Summary
BOTTOM - -
-           
MIDDLE - -
-           
TOP - -
-           
-  - - - - - - - - - - - - - - - -
-Method Summary
-static GraphView.LegendAlignvalueOf(java.lang.String name) - -
-          Returns the enum constant of this type with the specified name.
-static GraphView.LegendAlign[]values() - -
-          Returns an array containing the constants of this enum type, in -the order they are declared.
- - - - - - - -
Methods inherited from class java.lang.Enum
clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
- - - - - - - -
Methods inherited from class java.lang.Object
getClass, notify, notifyAll, wait, wait, wait
-  -

- - - - - - - - -
-Enum Constant Detail
- -

-TOP

-
-public static final GraphView.LegendAlign TOP
-
-
-
-
-
- -

-MIDDLE

-
-public static final GraphView.LegendAlign MIDDLE
-
-
-
-
-
- -

-BOTTOM

-
-public static final GraphView.LegendAlign BOTTOM
-
-
-
-
- - - - - - - - -
-Method Detail
- -

-values

-
-public static GraphView.LegendAlign[] values()
-
-
Returns an array containing the constants of this enum type, in -the order they are declared. This method may be used to iterate -over the constants as follows: -
-for (GraphView.LegendAlign c : GraphView.LegendAlign.values())
-    System.out.println(c);
-
-

-

- -
Returns:
an array containing the constants of this enum type, in -the order they are declared
-
-
-
- -

-valueOf

-
-public static GraphView.LegendAlign valueOf(java.lang.String name)
-
-
Returns the enum constant of this type with the specified name. -The string must match exactly an identifier used to declare an -enum constant in this type. (Extraneous whitespace characters are -not permitted.) -

-

-
Parameters:
name - the name of the enum constant to be returned. -
Returns:
the enum constant with the specified name -
Throws: -
java.lang.IllegalArgumentException - if this enum type has no constant -with the specified name -
java.lang.NullPointerException - if the argument is null
-
-
- -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/com/jjoe64/graphview/GraphView.html b/doc/com/jjoe64/graphview/GraphView.html deleted file mode 100644 index 08cad70c6..000000000 --- a/doc/com/jjoe64/graphview/GraphView.html +++ /dev/null @@ -1,1079 +0,0 @@ - - - - - - -GraphView - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- -

- -com.jjoe64.graphview -
-Class GraphView

-
-java.lang.Object
-  extended by LinearLayout
-      extended by com.jjoe64.graphview.GraphView
-
-
-
Direct Known Subclasses:
BarGraphView, LineGraphView
-
-
-
-
public abstract class GraphView
extends LinearLayout
- - -

-GraphView is a Android View for creating zoomable and scrollable graphs. - This is the abstract base class for all graphs. Extend this class and implement #drawSeries(Canvas, GraphViewDataInterface[], float, float, float, double, double, double, double, float) to display a custom graph. - Use LineGraphView for creating a line chart. -

- -

-

-
Author:
-
jjoe64 - jonas gehring - http://www.jjoe64.com - - Copyright (C) 2011 Jonas Gehring - Licensed under the GNU Lesser General Public License (LGPL) - http://www.gnu.org/licenses/lgpl.html
-
-
- -

- - - - - - - - - - - - - - - -
-Nested Class Summary
-static classGraphView.GraphViewData - -
-          one data set for a graph series
-static classGraphView.LegendAlign - -
-           
- - - - - - - - - - -
-Field Summary
-protected  Paintpaint - -
-           
-  - - - - - - - - - - - - - -
-Constructor Summary
GraphView(Context context, - AttributeSet attrs) - -
-           
GraphView(Context context, - java.lang.String title) - -
-           
-  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-Method Summary
- voidaddSeries(GraphViewSeries series) - -
-          add a series of data to the graph
-protected  voiddrawLegend(Canvas canvas, - float height, - float width) - -
-           
-protected abstract  voiddrawSeries(Canvas canvas, - GraphViewDataInterface[] values, - float graphwidth, - float graphheight, - float border, - double minX, - double minY, - double diffX, - double diffY, - float horstart, - GraphViewSeries.GraphViewSeriesStyle style) - -
-           
-protected  java.lang.StringformatLabel(double value, - boolean isValueX) - -
-          Deprecated. use setCustomLabelFormatter(CustomLabelFormatter)
- CustomLabelFormattergetCustomLabelFormatter() - -
-           
- GraphViewStylegetGraphViewStyle() - -
-           
- GraphView.LegendAligngetLegendAlign() - -
-          get the position of the legend
- floatgetLegendWidth() - -
-           
-protected  doublegetMaxX(boolean ignoreViewport) - -
-          returns the maximal X value of the current viewport (if viewport is set) - otherwise maximal X value of all data.
-protected  doublegetMaxY() - -
-          returns the maximal Y value of all data.
-protected  doublegetMinX(boolean ignoreViewport) - -
-          returns the minimal X value of the current viewport (if viewport is set) - otherwise minimal X value of all data.
-protected  doublegetMinY() - -
-          returns the minimal Y value of all data.
- booleanisDisableTouch() - -
-           
- booleanisScrollable() - -
-           
- booleanisShowLegend() - -
-           
- voidredrawAll() - -
-          forces graphview to invalide all views and caches.
- voidremoveAllSeries() - -
-          removes all series
- voidremoveSeries(GraphViewSeries series) - -
-          removes a series
- voidremoveSeries(int index) - -
-          removes series
- voidscrollToEnd() - -
-          scrolls to the last x-value
- voidsetCustomLabelFormatter(CustomLabelFormatter customLabelFormatter) - -
-          set a custom label formatter
- voidsetDisableTouch(boolean disableTouch) - -
-          The user can disable any touch gestures, this is useful if you are using a real time graph, but don't want the user to interact
- voidsetGraphViewStyle(GraphViewStyle style) - -
-          set custom graphview style
- voidsetHorizontalLabels(java.lang.String[] horlabels) - -
-          set's static horizontal labels (from left to right)
- voidsetLegendAlign(GraphView.LegendAlign legendAlign) - -
-          legend position
- voidsetLegendWidth(float legendWidth) - -
-          legend width
- voidsetManualYAxis(boolean manualYAxis) - -
-          you have to set the bounds setManualYAxisBounds(double, double).
- voidsetManualYAxisBounds(double max, - double min) - -
-          set manual Y axis limit
- voidsetScalable(boolean scalable) - -
-          this forces scrollable = true
- voidsetScrollable(boolean scrollable) - -
-          the user can scroll (horizontal) the graph.
- voidsetShowLegend(boolean showLegend) - -
-           
- voidsetTitle(java.lang.String title) - -
-          sets the title of graphview
- voidsetVerticalLabels(java.lang.String[] verlabels) - -
-          set's static vertical labels (from top to bottom)
- voidsetViewPort(double start, - double size) - -
-          set's the viewport for the graph.
- - - - - - - -
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
-  -

- - - - - - - - -
-Field Detail
- -

-paint

-
-protected final Paint paint
-
-
-
-
- - - - - - - - -
-Constructor Detail
- -

-GraphView

-
-public GraphView(Context context,
-                 AttributeSet attrs)
-
-
-
- -

-GraphView

-
-public GraphView(Context context,
-                 java.lang.String title)
-
-
-
Parameters:
context -
title - [optional]
-
- - - - - - - - -
-Method Detail
- -

-addSeries

-
-public void addSeries(GraphViewSeries series)
-
-
add a series of data to the graph -

-

-
Parameters:
series -
-
-
-
- -

-drawLegend

-
-protected void drawLegend(Canvas canvas,
-                          float height,
-                          float width)
-
-
-
-
-
-
- -

-drawSeries

-
-protected abstract void drawSeries(Canvas canvas,
-                                   GraphViewDataInterface[] values,
-                                   float graphwidth,
-                                   float graphheight,
-                                   float border,
-                                   double minX,
-                                   double minY,
-                                   double diffX,
-                                   double diffY,
-                                   float horstart,
-                                   GraphViewSeries.GraphViewSeriesStyle style)
-
-
-
-
-
-
- -

-formatLabel

-
-@Deprecated
-protected java.lang.String formatLabel(double value,
-                                                  boolean isValueX)
-
-
Deprecated. use setCustomLabelFormatter(CustomLabelFormatter) -

-

formats the label - use #setCustomLabelFormatter or static labels if you want custom labels -

-

-
Parameters:
value - x and y values
isValueX - if false, value y wants to be formatted -
Returns:
value to display
-
-
-
- -

-getCustomLabelFormatter

-
-public CustomLabelFormatter getCustomLabelFormatter()
-
-
- -
Returns:
the custom label formatter, if there is one. otherwise null
-
-
-
- -

-getGraphViewStyle

-
-public GraphViewStyle getGraphViewStyle()
-
-
- -
Returns:
the graphview style. it will never be null.
-
-
-
- -

-getLegendAlign

-
-public GraphView.LegendAlign getLegendAlign()
-
-
get the position of the legend -

-

- -
Returns:
-
-
-
- -

-getLegendWidth

-
-public float getLegendWidth()
-
-
- -
Returns:
legend width
-
-
-
- -

-getMaxX

-
-protected double getMaxX(boolean ignoreViewport)
-
-
returns the maximal X value of the current viewport (if viewport is set) - otherwise maximal X value of all data. -

-

-
Parameters:
ignoreViewport - warning: only override this, if you really know want you're doing!
-
-
-
- -

-getMaxY

-
-protected double getMaxY()
-
-
returns the maximal Y value of all data. - - warning: only override this, if you really know want you're doing! -

-

-
-
-
-
- -

-getMinX

-
-protected double getMinX(boolean ignoreViewport)
-
-
returns the minimal X value of the current viewport (if viewport is set) - otherwise minimal X value of all data. -

-

-
Parameters:
ignoreViewport - warning: only override this, if you really know want you're doing!
-
-
-
- -

-getMinY

-
-protected double getMinY()
-
-
returns the minimal Y value of all data. - - warning: only override this, if you really know want you're doing! -

-

-
-
-
-
- -

-isDisableTouch

-
-public boolean isDisableTouch()
-
-
-
-
-
-
- -

-isScrollable

-
-public boolean isScrollable()
-
-
-
-
-
-
- -

-isShowLegend

-
-public boolean isShowLegend()
-
-
-
-
-
-
- -

-redrawAll

-
-public void redrawAll()
-
-
forces graphview to invalide all views and caches. - Normally there is no need to call this manually. -

-

-
-
-
-
- -

-removeAllSeries

-
-public void removeAllSeries()
-
-
removes all series -

-

-
-
-
-
- -

-removeSeries

-
-public void removeSeries(GraphViewSeries series)
-
-
removes a series -

-

-
Parameters:
series - series to remove
-
-
-
- -

-removeSeries

-
-public void removeSeries(int index)
-
-
removes series -

-

-
Parameters:
index -
-
-
-
- -

-scrollToEnd

-
-public void scrollToEnd()
-
-
scrolls to the last x-value -

-

- -
Throws: -
java.lang.IllegalStateException - if scrollable == false
-
-
-
- -

-setCustomLabelFormatter

-
-public void setCustomLabelFormatter(CustomLabelFormatter customLabelFormatter)
-
-
set a custom label formatter -

-

-
Parameters:
customLabelFormatter -
-
-
-
- -

-setDisableTouch

-
-public void setDisableTouch(boolean disableTouch)
-
-
The user can disable any touch gestures, this is useful if you are using a real time graph, but don't want the user to interact -

-

-
Parameters:
disableTouch -
-
-
-
- -

-setGraphViewStyle

-
-public void setGraphViewStyle(GraphViewStyle style)
-
-
set custom graphview style -

-

-
Parameters:
style -
-
-
-
- -

-setHorizontalLabels

-
-public void setHorizontalLabels(java.lang.String[] horlabels)
-
-
set's static horizontal labels (from left to right) -

-

-
Parameters:
horlabels - if null, labels were generated automatically
-
-
-
- -

-setLegendAlign

-
-public void setLegendAlign(GraphView.LegendAlign legendAlign)
-
-
legend position -

-

-
Parameters:
legendAlign -
-
-
-
- -

-setLegendWidth

-
-public void setLegendWidth(float legendWidth)
-
-
legend width -

-

-
Parameters:
legendWidth -
-
-
-
- -

-setManualYAxis

-
-public void setManualYAxis(boolean manualYAxis)
-
-
you have to set the bounds setManualYAxisBounds(double, double). That automatically enables manualYAxis-flag. - if you want to disable the menual y axis, call this method with false. -

-

-
Parameters:
manualYAxis -
-
-
-
- -

-setManualYAxisBounds

-
-public void setManualYAxisBounds(double max,
-                                 double min)
-
-
set manual Y axis limit -

-

-
Parameters:
max -
min -
-
-
-
- -

-setScalable

-
-public void setScalable(boolean scalable)
-
-
this forces scrollable = true -

-

-
Parameters:
scalable -
-
-
-
- -

-setScrollable

-
-public void setScrollable(boolean scrollable)
-
-
the user can scroll (horizontal) the graph. This is only useful if you use a viewport setViewPort(double, double) which doesn't displays all data. -

-

-
Parameters:
scrollable -
-
-
-
- -

-setShowLegend

-
-public void setShowLegend(boolean showLegend)
-
-
-
-
-
-
- -

-setTitle

-
-public void setTitle(java.lang.String title)
-
-
sets the title of graphview -

-

-
Parameters:
title -
-
-
-
- -

-setVerticalLabels

-
-public void setVerticalLabels(java.lang.String[] verlabels)
-
-
set's static vertical labels (from top to bottom) -

-

-
Parameters:
verlabels - if null, labels were generated automatically
-
-
-
- -

-setViewPort

-
-public void setViewPort(double start,
-                        double size)
-
-
set's the viewport for the graph. -

-

-
Parameters:
start - x-value
size -
See Also:
to limit the y-viewport
-
-
- -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/com/jjoe64/graphview/GraphViewDataInterface.html b/doc/com/jjoe64/graphview/GraphViewDataInterface.html deleted file mode 100644 index d8d9bf8cc..000000000 --- a/doc/com/jjoe64/graphview/GraphViewDataInterface.html +++ /dev/null @@ -1,234 +0,0 @@ - - - - - - -GraphViewDataInterface - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- -

- -com.jjoe64.graphview -
-Interface GraphViewDataInterface

-
-
All Known Implementing Classes:
GraphView.GraphViewData
-
-
-
-
public interface GraphViewDataInterface
- - -

-the base interface for the graphview data. - you can use your own data models, when they implement - this interface. -

- -

-


- -

- - - - - - - - - - - - - - - - -
-Method Summary
- doublegetX() - -
-           
- doublegetY() - -
-           
-  -

- - - - - - - - -
-Method Detail
- -

-getX

-
-double getX()
-
-
-
-
-
-
- -

-getY

-
-double getY()
-
-
-
-
-
- -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/com/jjoe64/graphview/GraphViewSeries.GraphViewSeriesStyle.html b/doc/com/jjoe64/graphview/GraphViewSeries.GraphViewSeriesStyle.html deleted file mode 100644 index 0f471a63d..000000000 --- a/doc/com/jjoe64/graphview/GraphViewSeries.GraphViewSeriesStyle.html +++ /dev/null @@ -1,351 +0,0 @@ - - - - - - -GraphViewSeries.GraphViewSeriesStyle - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- -

- -com.jjoe64.graphview -
-Class GraphViewSeries.GraphViewSeriesStyle

-
-java.lang.Object
-  extended by com.jjoe64.graphview.GraphViewSeries.GraphViewSeriesStyle
-
-
-
Enclosing class:
GraphViewSeries
-
-
-
-
public static class GraphViewSeries.GraphViewSeriesStyle
extends java.lang.Object
- - -

-graph series style: color and thickness -

- -

-


- -

- - - - - - - - - - - - - - - -
-Field Summary
- intcolor - -
-           
- intthickness - -
-           
-  - - - - - - - - - - - - - -
-Constructor Summary
GraphViewSeries.GraphViewSeriesStyle() - -
-           
GraphViewSeries.GraphViewSeriesStyle(int color, - int thickness) - -
-           
-  - - - - - - - - - - - - - - - -
-Method Summary
- ValueDependentColorgetValueDependentColor() - -
-           
- voidsetValueDependentColor(ValueDependentColor valueDependentColor) - -
-          the color depends on the value of the data.
- - - - - - - -
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
-  -

- - - - - - - - -
-Field Detail
- -

-color

-
-public int color
-
-
-
-
-
- -

-thickness

-
-public int thickness
-
-
-
-
- - - - - - - - -
-Constructor Detail
- -

-GraphViewSeries.GraphViewSeriesStyle

-
-public GraphViewSeries.GraphViewSeriesStyle()
-
-
-
- -

-GraphViewSeries.GraphViewSeriesStyle

-
-public GraphViewSeries.GraphViewSeriesStyle(int color,
-                                            int thickness)
-
-
- - - - - - - - -
-Method Detail
- -

-getValueDependentColor

-
-public ValueDependentColor getValueDependentColor()
-
-
-
-
-
-
- -

-setValueDependentColor

-
-public void setValueDependentColor(ValueDependentColor valueDependentColor)
-
-
the color depends on the value of the data. - only possible in BarGraphView -

-

-
Parameters:
valueDependentColor -
-
-
- -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/com/jjoe64/graphview/GraphViewSeries.html b/doc/com/jjoe64/graphview/GraphViewSeries.html deleted file mode 100644 index ebcf39402..000000000 --- a/doc/com/jjoe64/graphview/GraphViewSeries.html +++ /dev/null @@ -1,408 +0,0 @@ - - - - - - -GraphViewSeries - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- -

- -com.jjoe64.graphview -
-Class GraphViewSeries

-
-java.lang.Object
-  extended by com.jjoe64.graphview.GraphViewSeries
-
-
-
-
public class GraphViewSeries
extends java.lang.Object
- - -

-a graphview series. - holds the data, description and styles -

- -

-


- -

- - - - - - - - - - - -
-Nested Class Summary
-static classGraphViewSeries.GraphViewSeriesStyle - -
-          graph series style: color and thickness
-  - - - - - - - - - - - - - -
-Constructor Summary
GraphViewSeries(GraphViewDataInterface[] values) - -
-           
GraphViewSeries(java.lang.String description, - GraphViewSeries.GraphViewSeriesStyle style, - GraphViewDataInterface[] values) - -
-           
-  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-Method Summary
- voidaddGraphView(GraphView graphView) - -
-          this graphview will be redrawn if data changes
- voidappendData(GraphViewDataInterface value, - boolean scrollToEnd) - -
-          Deprecated. please use appendData(GraphViewDataInterface, boolean, int) to avoid memory overflow
- voidappendData(GraphViewDataInterface value, - boolean scrollToEnd, - int maxDataCount) - -
-          add one data to current data
- GraphViewSeries.GraphViewSeriesStylegetStyle() - -
-           
- voidremoveGraphView(GraphView graphView) - -
-          you should use GraphView.removeSeries(GraphViewSeries)
- voidresetData(GraphViewDataInterface[] values) - -
-          clears the current data and set the new.
- - - - - - - -
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
-  -

- - - - - - - - -
-Constructor Detail
- -

-GraphViewSeries

-
-public GraphViewSeries(GraphViewDataInterface[] values)
-
-
-
- -

-GraphViewSeries

-
-public GraphViewSeries(java.lang.String description,
-                       GraphViewSeries.GraphViewSeriesStyle style,
-                       GraphViewDataInterface[] values)
-
-
- - - - - - - - -
-Method Detail
- -

-addGraphView

-
-public void addGraphView(GraphView graphView)
-
-
this graphview will be redrawn if data changes -

-

-
Parameters:
graphView -
-
-
-
- -

-appendData

-
-@Deprecated
-public void appendData(GraphViewDataInterface value,
-                                  boolean scrollToEnd)
-
-
Deprecated. please use appendData(GraphViewDataInterface, boolean, int) to avoid memory overflow -

-

add one data to current data -

-

-
Parameters:
value - the new data to append
scrollToEnd - true => graphview will scroll to the end (maxX)
-
-
-
- -

-appendData

-
-public void appendData(GraphViewDataInterface value,
-                       boolean scrollToEnd,
-                       int maxDataCount)
-
-
add one data to current data -

-

-
Parameters:
value - the new data to append
scrollToEnd - true => graphview will scroll to the end (maxX)
maxDataCount - if max data count is reached, the oldest data value will be lost
-
-
-
- -

-getStyle

-
-public GraphViewSeries.GraphViewSeriesStyle getStyle()
-
-
- -
Returns:
series styles. never null
-
-
-
- -

-removeGraphView

-
-public void removeGraphView(GraphView graphView)
-
-
you should use GraphView.removeSeries(GraphViewSeries) -

-

-
Parameters:
graphView -
-
-
-
- -

-resetData

-
-public void resetData(GraphViewDataInterface[] values)
-
-
clears the current data and set the new. - redraws the graphview(s) -

-

-
Parameters:
values - new data
-
-
- -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/com/jjoe64/graphview/GraphViewStyle.html b/doc/com/jjoe64/graphview/GraphViewStyle.html deleted file mode 100644 index 8f3c7f2df..000000000 --- a/doc/com/jjoe64/graphview/GraphViewStyle.html +++ /dev/null @@ -1,522 +0,0 @@ - - - - - - -GraphViewStyle - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- -

- -com.jjoe64.graphview -
-Class GraphViewStyle

-
-java.lang.Object
-  extended by com.jjoe64.graphview.GraphViewStyle
-
-
-
-
public class GraphViewStyle
extends java.lang.Object
- - -

-Styles for the GraphView - Important: Use GraphViewSeries.GraphViewSeriesStyle for series-specify styling -

- -

-


- -

- - - - - - - - - - - - - - -
-Constructor Summary
GraphViewStyle() - -
-           
GraphViewStyle(int vLabelsColor, - int hLabelsColor, - int gridColor) - -
-           
-  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-Method Summary
- intgetGridColor() - -
-           
- intgetHorizontalLabelsColor() - -
-           
- intgetNumHorizontalLabels() - -
-           
- intgetNumVerticalLabels() - -
-           
- floatgetTextSize() - -
-           
- intgetVerticalLabelsColor() - -
-           
- intgetVerticalLabelsWidth() - -
-           
- voidsetGridColor(int c) - -
-           
- voidsetHorizontalLabelsColor(int c) - -
-           
- voidsetNumHorizontalLabels(int numHorizontalLabels) - -
-           
- voidsetNumVerticalLabels(int numVerticalLabels) - -
-           
- voidsetTextSize(float textSize) - -
-           
- voidsetVerticalLabelsColor(int c) - -
-           
- voidsetVerticalLabelsWidth(int verticalLabelsWidth) - -
-           
- - - - - - - -
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
-  -

- - - - - - - - -
-Constructor Detail
- -

-GraphViewStyle

-
-public GraphViewStyle()
-
-
-
- -

-GraphViewStyle

-
-public GraphViewStyle(int vLabelsColor,
-                      int hLabelsColor,
-                      int gridColor)
-
-
- - - - - - - - -
-Method Detail
- -

-getGridColor

-
-public int getGridColor()
-
-
-
-
-
-
- -

-getHorizontalLabelsColor

-
-public int getHorizontalLabelsColor()
-
-
-
-
-
-
- -

-getNumHorizontalLabels

-
-public int getNumHorizontalLabels()
-
-
-
-
-
-
- -

-getNumVerticalLabels

-
-public int getNumVerticalLabels()
-
-
-
-
-
-
- -

-getTextSize

-
-public float getTextSize()
-
-
-
-
-
-
- -

-getVerticalLabelsColor

-
-public int getVerticalLabelsColor()
-
-
-
-
-
-
- -

-getVerticalLabelsWidth

-
-public int getVerticalLabelsWidth()
-
-
-
-
-
-
- -

-setGridColor

-
-public void setGridColor(int c)
-
-
-
-
-
-
- -

-setHorizontalLabelsColor

-
-public void setHorizontalLabelsColor(int c)
-
-
-
-
-
-
- -

-setNumHorizontalLabels

-
-public void setNumHorizontalLabels(int numHorizontalLabels)
-
-
-
Parameters:
numHorizontalLabels - 0 = auto
-
-
-
- -

-setNumVerticalLabels

-
-public void setNumVerticalLabels(int numVerticalLabels)
-
-
-
Parameters:
numVerticalLabels - 0 = auto
-
-
-
- -

-setTextSize

-
-public void setTextSize(float textSize)
-
-
-
-
-
-
- -

-setVerticalLabelsColor

-
-public void setVerticalLabelsColor(int c)
-
-
-
-
-
-
- -

-setVerticalLabelsWidth

-
-public void setVerticalLabelsWidth(int verticalLabelsWidth)
-
-
-
Parameters:
verticalLabelsWidth - 0 = auto
-
-
- -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/com/jjoe64/graphview/LineGraphView.html b/doc/com/jjoe64/graphview/LineGraphView.html deleted file mode 100644 index d18b516b8..000000000 --- a/doc/com/jjoe64/graphview/LineGraphView.html +++ /dev/null @@ -1,421 +0,0 @@ - - - - - - -LineGraphView - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- -

- -com.jjoe64.graphview -
-Class LineGraphView

-
-java.lang.Object
-  extended by LinearLayout
-      extended by com.jjoe64.graphview.GraphView
-          extended by com.jjoe64.graphview.LineGraphView
-
-
-
-
public class LineGraphView
extends GraphView
- - -

-Line Graph View. This draws a line chart. -

- -

-


- -

- - - - - - - -
-Nested Class Summary
- - - - - - - -
Nested classes/interfaces inherited from class com.jjoe64.graphview.GraphView
GraphView.GraphViewData, GraphView.LegendAlign
-  - - - - - - - -
-Field Summary
- - - - - - - -
Fields inherited from class com.jjoe64.graphview.GraphView
paint
-  - - - - - - - - - - - - - -
-Constructor Summary
LineGraphView(Context context, - AttributeSet attrs) - -
-           
LineGraphView(Context context, - java.lang.String title) - -
-           
-  - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-Method Summary
- voiddrawSeries(Canvas canvas, - GraphViewDataInterface[] values, - float graphwidth, - float graphheight, - float border, - double minX, - double minY, - double diffX, - double diffY, - float horstart, - GraphViewSeries.GraphViewSeriesStyle style) - -
-           
- intgetBackgroundColor() - -
-           
- booleangetDrawBackground() - -
-           
- voidsetBackgroundColor(int color) - -
-           
- voidsetDrawBackground(boolean drawBackground) - -
-           
- - - - - - - -
Methods inherited from class com.jjoe64.graphview.GraphView
addSeries, drawLegend, formatLabel, getCustomLabelFormatter, getGraphViewStyle, getLegendAlign, getLegendWidth, getMaxX, getMaxY, getMinX, getMinY, isDisableTouch, isScrollable, isShowLegend, redrawAll, removeAllSeries, removeSeries, removeSeries, scrollToEnd, setCustomLabelFormatter, setDisableTouch, setGraphViewStyle, setHorizontalLabels, setLegendAlign, setLegendWidth, setManualYAxis, setManualYAxisBounds, setScalable, setScrollable, setShowLegend, setTitle, setVerticalLabels, setViewPort
- - - - - - - -
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
-  -

- - - - - - - - -
-Constructor Detail
- -

-LineGraphView

-
-public LineGraphView(Context context,
-                     AttributeSet attrs)
-
-
-
- -

-LineGraphView

-
-public LineGraphView(Context context,
-                     java.lang.String title)
-
-
- - - - - - - - -
-Method Detail
- -

-drawSeries

-
-public void drawSeries(Canvas canvas,
-                       GraphViewDataInterface[] values,
-                       float graphwidth,
-                       float graphheight,
-                       float border,
-                       double minX,
-                       double minY,
-                       double diffX,
-                       double diffY,
-                       float horstart,
-                       GraphViewSeries.GraphViewSeriesStyle style)
-
-
-
Specified by:
drawSeries in class GraphView
-
-
-
-
-
-
- -

-getBackgroundColor

-
-public int getBackgroundColor()
-
-
-
-
-
-
- -

-getDrawBackground

-
-public boolean getDrawBackground()
-
-
-
-
-
-
- -

-setBackgroundColor

-
-public void setBackgroundColor(int color)
-
-
-
-
-
-
- -

-setDrawBackground

-
-public void setDrawBackground(boolean drawBackground)
-
-
-
Parameters:
drawBackground - true for a light blue background under the graph line
-
-
- -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/com/jjoe64/graphview/ValueDependentColor.html b/doc/com/jjoe64/graphview/ValueDependentColor.html deleted file mode 100644 index 7c13ec07b..000000000 --- a/doc/com/jjoe64/graphview/ValueDependentColor.html +++ /dev/null @@ -1,211 +0,0 @@ - - - - - - -ValueDependentColor - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- -

- -com.jjoe64.graphview -
-Interface ValueDependentColor

-
-
-
public interface ValueDependentColor
- - -

-you can change the color depending on the value. - takes only effect in BarGraphView -

- -

-


- -

- - - - - - - - - - - - -
-Method Summary
- intget(GraphViewDataInterface data) - -
-           
-  -

- - - - - - - - -
-Method Detail
- -

-get

-
-int get(GraphViewDataInterface data)
-
-
-
-
-
- -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/com/jjoe64/graphview/class-use/GraphView.GraphViewData.html b/doc/com/jjoe64/graphview/class-use/GraphView.GraphViewData.html deleted file mode 100644 index dddb4b9ac..000000000 --- a/doc/com/jjoe64/graphview/class-use/GraphView.GraphViewData.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - -Uses of Class com.jjoe64.graphview.GraphView.GraphViewData - - - - - - - - - - - - -
- - - - - - - - - - - - - - - -
- -
- - - -
-
-

-Uses of Class
com.jjoe64.graphview.GraphView.GraphViewData

-
- - - - - -
-Uses of GraphView.GraphViewData in com.jjoe64.graphview
-  -

- - - - - - - - -
Constructors in com.jjoe64.graphview with parameters of type GraphView.GraphViewData
GraphView(Context context, - GraphView.GraphViewData[] values, - java.lang.String title, - java.lang.String[] horlabels, - java.lang.String[] verlabels) - -
-           
-  -

-


- - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/com/jjoe64/graphview/class-use/GraphView.html b/doc/com/jjoe64/graphview/class-use/GraphView.html deleted file mode 100644 index 51b14782c..000000000 --- a/doc/com/jjoe64/graphview/class-use/GraphView.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - -Uses of Class com.jjoe64.graphview.GraphView - - - - - - - - - - - - -
- - - - - - - - - - - - - - - -
- -
- - - -
-
-

-Uses of Class
com.jjoe64.graphview.GraphView

-
-No usage of com.jjoe64.graphview.GraphView -

-


- - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/com/jjoe64/graphview/compatible/RealScaleGestureDetector.html b/doc/com/jjoe64/graphview/compatible/RealScaleGestureDetector.html deleted file mode 100644 index 821853340..000000000 --- a/doc/com/jjoe64/graphview/compatible/RealScaleGestureDetector.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - - - -RealScaleGestureDetector - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- -

- -com.jjoe64.graphview.compatible -
-Class RealScaleGestureDetector

-
-java.lang.Object
-  extended by com.jjoe64.graphview.compatible.ScaleGestureDetector
-      extended by com.jjoe64.graphview.compatible.RealScaleGestureDetector
-
-
-
-

- -

- - - - - - - -
-Nested Class Summary
- - - - - - - -
Nested classes/interfaces inherited from class com.jjoe64.graphview.compatible.ScaleGestureDetector
ScaleGestureDetector.SimpleOnScaleGestureListener
-  - - - - - - - - - -
-Constructor Summary
RealScaleGestureDetector(Context context, - ScaleGestureDetector fakeScaleGestureDetector, - ScaleGestureDetector.SimpleOnScaleGestureListener fakeListener) - -
-           - - - - - - - -
-Method Summary
- - - - - - - -
Methods inherited from class com.jjoe64.graphview.compatible.ScaleGestureDetector
getScaleFactor, isInProgress, onTouchEvent
- - - - - - - -
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
-  -

- - - - - - - - -
-Constructor Detail
- -

-RealScaleGestureDetector

-
-public RealScaleGestureDetector(Context context,
-                                ScaleGestureDetector fakeScaleGestureDetector,
-                                ScaleGestureDetector.SimpleOnScaleGestureListener fakeListener)
-
-
- -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/com/jjoe64/graphview/compatible/ScaleGestureDetector.SimpleOnScaleGestureListener.html b/doc/com/jjoe64/graphview/compatible/ScaleGestureDetector.SimpleOnScaleGestureListener.html deleted file mode 100644 index 56a153a95..000000000 --- a/doc/com/jjoe64/graphview/compatible/ScaleGestureDetector.SimpleOnScaleGestureListener.html +++ /dev/null @@ -1,209 +0,0 @@ - - - - - - -ScaleGestureDetector.SimpleOnScaleGestureListener - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- -

- -com.jjoe64.graphview.compatible -
-Interface ScaleGestureDetector.SimpleOnScaleGestureListener

-
-
Enclosing class:
ScaleGestureDetector
-
-
-
-
public static interface ScaleGestureDetector.SimpleOnScaleGestureListener
- - -

-


- -

- - - - - - - - - - - - -
-Method Summary
- booleanonScale(ScaleGestureDetector detector) - -
-           
-  -

- - - - - - - - -
-Method Detail
- -

-onScale

-
-boolean onScale(ScaleGestureDetector detector)
-
-
-
-
-
- -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/com/jjoe64/graphview/compatible/ScaleGestureDetector.html b/doc/com/jjoe64/graphview/compatible/ScaleGestureDetector.html deleted file mode 100644 index d1fe400bd..000000000 --- a/doc/com/jjoe64/graphview/compatible/ScaleGestureDetector.html +++ /dev/null @@ -1,314 +0,0 @@ - - - - - - -ScaleGestureDetector - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- -

- -com.jjoe64.graphview.compatible -
-Class ScaleGestureDetector

-
-java.lang.Object
-  extended by com.jjoe64.graphview.compatible.ScaleGestureDetector
-
-
-
Direct Known Subclasses:
RealScaleGestureDetector
-
-
-
-
public class ScaleGestureDetector
extends java.lang.Object
- - -

-


- -

- - - - - - - - - - - -
-Nested Class Summary
-static interfaceScaleGestureDetector.SimpleOnScaleGestureListener - -
-           
-  - - - - - - - - - - -
-Constructor Summary
ScaleGestureDetector(Context context, - ScaleGestureDetector.SimpleOnScaleGestureListener simpleOnScaleGestureListener) - -
-           
-  - - - - - - - - - - - - - - - - - - - -
-Method Summary
- doublegetScaleFactor() - -
-           
- booleanisInProgress() - -
-           
- voidonTouchEvent(MotionEvent event) - -
-           
- - - - - - - -
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
-  -

- - - - - - - - -
-Constructor Detail
- -

-ScaleGestureDetector

-
-public ScaleGestureDetector(Context context,
-                            ScaleGestureDetector.SimpleOnScaleGestureListener simpleOnScaleGestureListener)
-
-
-
Parameters:
context -
simpleOnScaleGestureListener -
-
- - - - - - - - -
-Method Detail
- -

-getScaleFactor

-
-public double getScaleFactor()
-
-
-
-
-
-
- -

-isInProgress

-
-public boolean isInProgress()
-
-
-
-
-
-
- -

-onTouchEvent

-
-public void onTouchEvent(MotionEvent event)
-
-
-
-
-
- -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/com/jjoe64/graphview/compatible/package-frame.html b/doc/com/jjoe64/graphview/compatible/package-frame.html deleted file mode 100644 index 68eaca084..000000000 --- a/doc/com/jjoe64/graphview/compatible/package-frame.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - -com.jjoe64.graphview.compatible - - - - - - - - - - - -com.jjoe64.graphview.compatible - - - - -
-Interfaces  - -
-ScaleGestureDetector.SimpleOnScaleGestureListener
- - - - - - -
-Classes  - -
-RealScaleGestureDetector -
-ScaleGestureDetector
- - - - diff --git a/doc/com/jjoe64/graphview/compatible/package-summary.html b/doc/com/jjoe64/graphview/compatible/package-summary.html deleted file mode 100644 index 0b5f6924f..000000000 --- a/doc/com/jjoe64/graphview/compatible/package-summary.html +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - -com.jjoe64.graphview.compatible - - - - - - - - - - - - -
- - - - - - - - - - - - - - - -
- -
- - - -
-

-Package com.jjoe64.graphview.compatible -

- - - - - - - - - -
-Interface Summary
ScaleGestureDetector.SimpleOnScaleGestureListener 
-  - -

- - - - - - - -
-Class Summary
RealScaleGestureDetector
-
-
- - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/com/jjoe64/graphview/compatible/package-tree.html b/doc/com/jjoe64/graphview/compatible/package-tree.html deleted file mode 100644 index 45d66fdc3..000000000 --- a/doc/com/jjoe64/graphview/compatible/package-tree.html +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - -com.jjoe64.graphview.compatible Class Hierarchy - - - - - - - - - - - - -
- - - - - - - - - - - - - - - -
- -
- - - -
-
-

-Hierarchy For Package com.jjoe64.graphview.compatible -

-
-
-
Package Hierarchies:
All Packages
-
-

-Class Hierarchy -

- -

-Interface Hierarchy -

- -
- - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/com/jjoe64/graphview/package-frame.html b/doc/com/jjoe64/graphview/package-frame.html deleted file mode 100644 index e74175d27..000000000 --- a/doc/com/jjoe64/graphview/package-frame.html +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - -com.jjoe64.graphview - - - - - - - - - - - -com.jjoe64.graphview - - - - -
-Interfaces  - -
-CustomLabelFormatter -
-GraphViewDataInterface -
-ValueDependentColor
- - - - - - -
-Classes  - -
-BarGraphView -
-GraphView -
-GraphView.GraphViewData -
-GraphViewSeries -
-GraphViewSeries.GraphViewSeriesStyle -
-GraphViewStyle -
-LineGraphView
- - - - - - -
-Enums  - -
-GraphView.LegendAlign
- - - - diff --git a/doc/com/jjoe64/graphview/package-summary.html b/doc/com/jjoe64/graphview/package-summary.html deleted file mode 100644 index 450f240ad..000000000 --- a/doc/com/jjoe64/graphview/package-summary.html +++ /dev/null @@ -1,219 +0,0 @@ - - - - - - -com.jjoe64.graphview - - - - - - - - - - - - -
- - - - - - - - - - - - - - - -
- -
- - - -
-

-Package com.jjoe64.graphview -

- - - - - - - - - - - - - - - - - -
-Interface Summary
CustomLabelFormatterif you want to show different labels, - you can use this label formatter.
GraphViewDataInterfacethe base interface for the graphview data.
ValueDependentColoryou can change the color depending on the value.
-  - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-Class Summary
BarGraphViewDraws a Bar Chart
GraphViewGraphView is a Android View for creating zoomable and scrollable graphs.
GraphView.GraphViewDataone data set for a graph series
GraphViewSeriesa graphview series.
GraphViewSeries.GraphViewSeriesStylegraph series style: color and thickness
GraphViewStyleStyles for the GraphView - Important: Use GraphViewSeries.GraphViewSeriesStyle for series-specify styling
LineGraphViewLine Graph View.
-  - -

- - - - - - - - - -
-Enum Summary
GraphView.LegendAlign 
-  - -

-

-
-
- - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/com/jjoe64/graphview/package-tree.html b/doc/com/jjoe64/graphview/package-tree.html deleted file mode 100644 index 204461503..000000000 --- a/doc/com/jjoe64/graphview/package-tree.html +++ /dev/null @@ -1,173 +0,0 @@ - - - - - - -com.jjoe64.graphview Class Hierarchy - - - - - - - - - - - - -
- - - - - - - - - - - - - - - -
- -
- - - -
-
-

-Hierarchy For Package com.jjoe64.graphview -

-
-
-
Package Hierarchies:
All Packages
-
-

-Class Hierarchy -

- -

-Interface Hierarchy -

- -

-Enum Hierarchy -

-
    -
  • java.lang.Object
      -
    • java.lang.Enum<E> (implements java.lang.Comparable<T>, java.io.Serializable) - -
    -
-
- - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/com/jjoe64/graphview/package-use.html b/doc/com/jjoe64/graphview/package-use.html deleted file mode 100644 index 1b07119e4..000000000 --- a/doc/com/jjoe64/graphview/package-use.html +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - -Uses of Package com.jjoe64.graphview - - - - - - - - - - - - -
- - - - - - - - - - - - - - - -
- -
- - - -
-
-

-Uses of Package
com.jjoe64.graphview

-
- - - - - - - - -
-Classes in com.jjoe64.graphview used by com.jjoe64.graphview
GraphView.GraphViewData - -
-          one data set for the graph
-  -

-


- - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/constant-values.html b/doc/constant-values.html deleted file mode 100644 index 5f5525f39..000000000 --- a/doc/constant-values.html +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - -Constant Field Values - - - - - - - - - - - - -
- - - - - - - - - - - - - - - -
- -
- - - -
-
-

-Constant Field Values

-
-
-Contents
    -
- -
- - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/deprecated-list.html b/doc/deprecated-list.html deleted file mode 100644 index 690f8ba6d..000000000 --- a/doc/deprecated-list.html +++ /dev/null @@ -1,144 +0,0 @@ - - - - - - -Deprecated List - - - - - - - - - - - - -
- - - - - - - - - - - - - - - -
- -
- - - -
-
-

-Deprecated API

-
-
-Contents
    -
- -
- - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/help-doc.html b/doc/help-doc.html deleted file mode 100644 index e5440693f..000000000 --- a/doc/help-doc.html +++ /dev/null @@ -1,215 +0,0 @@ - - - - - - -API Help - - - - - - - - - - - - -
- - - - - - - - - - - - - - - -
- -
- - - -
-
-

-How This API Document Is Organized

-
-This API (Application Programming Interface) document has pages corresponding to the items in the navigation bar, described as follows.

-Package

-
- -

-Each package has a page that contains a list of its classes and interfaces, with a summary for each. This page can contain four categories:

    -
  • Interfaces (italic)
  • Classes
  • Enums
  • Exceptions
  • Errors
  • Annotation Types
-
-

-Class/Interface

-
- -

-Each class, interface, nested class and nested interface has its own separate page. Each of these pages has three sections consisting of a class/interface description, summary tables, and detailed member descriptions:

    -
  • Class inheritance diagram
  • Direct Subclasses
  • All Known Subinterfaces
  • All Known Implementing Classes
  • Class/interface declaration
  • Class/interface description -

    -

  • Nested Class Summary
  • Field Summary
  • Constructor Summary
  • Method Summary -

    -

  • Field Detail
  • Constructor Detail
  • Method Detail
-Each summary entry contains the first sentence from the detailed description for that item. The summary entries are alphabetical, while the detailed descriptions are in the order they appear in the source code. This preserves the logical groupings established by the programmer.
- -

-Annotation Type

-
- -

-Each annotation type has its own separate page with the following sections:

    -
  • Annotation Type declaration
  • Annotation Type description
  • Required Element Summary
  • Optional Element Summary
  • Element Detail
-
- -

-Enum

-
- -

-Each enum has its own separate page with the following sections:

    -
  • Enum declaration
  • Enum description
  • Enum Constant Summary
  • Enum Constant Detail
-
-

-Use

-
-Each documented package, class and interface has its own Use page. This page describes what packages, classes, methods, constructors and fields use any part of the given class or package. Given a class or interface A, its Use page includes subclasses of A, fields declared as A, methods that return A, and methods and constructors with parameters of type A. You can access this page by first going to the package, class or interface, then clicking on the "Use" link in the navigation bar.
-

-Tree (Class Hierarchy)

-
-There is a Class Hierarchy page for all packages, plus a hierarchy for each package. Each hierarchy page contains a list of classes and a list of interfaces. The classes are organized by inheritance structure starting with java.lang.Object. The interfaces do not inherit from java.lang.Object.
    -
  • When viewing the Overview page, clicking on "Tree" displays the hierarchy for all packages.
  • When viewing a particular package, class or interface page, clicking "Tree" displays the hierarchy for only that package.
-
-

-Deprecated API

-
-The Deprecated API page lists all of the API that have been deprecated. A deprecated API is not recommended for use, generally due to improvements, and a replacement API is usually given. Deprecated APIs may be removed in future implementations.
-

-Index

-
-The Index contains an alphabetic list of all classes, interfaces, constructors, methods, and fields.
-

-Prev/Next

-These links take you to the next or previous class, interface, package, or related page.

-Frames/No Frames

-These links show and hide the HTML frames. All pages are available with or without frames. -

-

-Serialized Form

-Each serializable or externalizable class has a description of its serialization fields and methods. This information is of interest to re-implementors, not to developers using the API. While there is no link in the navigation bar, you can get to this information by going to any serialized class and clicking "Serialized Form" in the "See also" section of the class description. -

-

-Constant Field Values

-The Constant Field Values page lists the static final fields and their values. -

- - -This help file applies to API documentation generated using the standard doclet. - -
-


- - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/index-files/index-1.html b/doc/index-files/index-1.html deleted file mode 100644 index 9ed722154..000000000 --- a/doc/index-files/index-1.html +++ /dev/null @@ -1,140 +0,0 @@ - - - - - - -C-Index - - - - - - - - - - - - -
- - - - - - - - - - - - - - - -
- -
- - - -C F G I S
-

-C

-
-
com.jjoe64.graphview - package com.jjoe64.graphview
 
-
- - - - - - - - - - - - - - - -
- -
- - - -C F G I S
- - - diff --git a/doc/index-files/index-2.html b/doc/index-files/index-2.html deleted file mode 100644 index 12a5f37c7..000000000 --- a/doc/index-files/index-2.html +++ /dev/null @@ -1,144 +0,0 @@ - - - - - - -F-Index - - - - - - - - - - - - -
- - - - - - - - - - - - - - - -
- -
- - - -C F G I S
-

-F

-
-
formatLabel(double) - -Method in class com.jjoe64.graphview.GraphView -
formats the label - can be overwritten -
-
- - - - - - - - - - - - - - - -
- -
- - - -C F G I S
- - - diff --git a/doc/index-files/index-3.html b/doc/index-files/index-3.html deleted file mode 100644 index 6bae1d2d1..000000000 --- a/doc/index-files/index-3.html +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - -G-Index - - - - - - - - - - - - -
- - - - - - - - - - - - - - - -
- -
- - - -C F G I S
-

-G

-
-
getDrawBackground() - -Method in class com.jjoe64.graphview.GraphView -
  -
GraphView - Class in com.jjoe64.graphview
GraphView creates a scaled line graph with x and y axis labels.
GraphView(Context, GraphView.GraphViewData[], String, String[], String[]) - -Constructor for class com.jjoe64.graphview.GraphView -
  -
GraphView.GraphViewData - Class in com.jjoe64.graphview
one data set for the graph
GraphView.GraphViewData(double, double) - -Constructor for class com.jjoe64.graphview.GraphView.GraphViewData -
  -
-
- - - - - - - - - - - - - - - -
- -
- - - -C F G I S
- - - diff --git a/doc/index-files/index-4.html b/doc/index-files/index-4.html deleted file mode 100644 index 18fc6ec4f..000000000 --- a/doc/index-files/index-4.html +++ /dev/null @@ -1,143 +0,0 @@ - - - - - - -I-Index - - - - - - - - - - - - -
- - - - - - - - - - - - - - - -
- -
- - - -C F G I S
-

-I

-
-
isScrollable() - -Method in class com.jjoe64.graphview.GraphView -
  -
-
- - - - - - - - - - - - - - - -
- -
- - - -C F G I S
- - - diff --git a/doc/index-files/index-5.html b/doc/index-files/index-5.html deleted file mode 100644 index 1a4c64dff..000000000 --- a/doc/index-files/index-5.html +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - -S-Index - - - - - - - - - - - - -
- - - - - - - - - - - - - - - -
- -
- - - -C F G I S
-

-S

-
-
setDrawBackground(boolean) - -Method in class com.jjoe64.graphview.GraphView -
  -
setScalable(boolean) - -Method in class com.jjoe64.graphview.GraphView -
this forces scrollable = true -
setScrollable(boolean) - -Method in class com.jjoe64.graphview.GraphView -
the user can scroll (horizontal) the graph. -
setViewPort(double, double) - -Method in class com.jjoe64.graphview.GraphView -
set's the viewport for the graph. -
-
- - - - - - - - - - - - - - - -
- -
- - - -C F G I S
- - - diff --git a/doc/index.html b/doc/index.html deleted file mode 100644 index 1c53a8b1a..000000000 --- a/doc/index.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - -Generated Documentation (Untitled) - - - - - - - - -<H2> -Frame Alert</H2> - -<P> -This document is designed to be viewed using the frames feature. If you see this message, you are using a non-frame-capable web client. -<BR> -Link to<A HREF="com/jjoe64/graphview/package-summary.html">Non-frame version.</A> - - - diff --git a/doc/overview-frame.html b/doc/overview-frame.html deleted file mode 100644 index 786d91796..000000000 --- a/doc/overview-frame.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - -Overview List - - - - - - - - - - - - - - - -
-
- - - - - -
All Classes -

- -Packages -
-com.jjoe64.graphview -
-com.jjoe64.graphview.compatible -
-

- -

-  - - diff --git a/doc/overview-tree.html b/doc/overview-tree.html deleted file mode 100644 index 5a08fd4f1..000000000 --- a/doc/overview-tree.html +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - -Class Hierarchy - - - - - - - - - - - - -


- - - - - - - - - - - - - - - -
- -
- - - -
-
-

-Hierarchy For All Packages

-
-
-
Package Hierarchies:
com.jjoe64.graphview
-
-

-Class Hierarchy -

- -
- - - - - - - - - - - - - - - -
- -
- - - -
- - - diff --git a/doc/package-list b/doc/package-list deleted file mode 100644 index ce425ccc3..000000000 --- a/doc/package-list +++ /dev/null @@ -1,2 +0,0 @@ -com.jjoe64.graphview -com.jjoe64.graphview.compatible diff --git a/doc/resources/inherit.gif b/doc/resources/inherit.gif deleted file mode 100644 index c814867a1..000000000 Binary files a/doc/resources/inherit.gif and /dev/null differ diff --git a/doc/stylesheet.css b/doc/stylesheet.css deleted file mode 100644 index 6ea9e5161..000000000 --- a/doc/stylesheet.css +++ /dev/null @@ -1,29 +0,0 @@ -/* Javadoc style sheet */ - -/* Define colors, fonts and other style attributes here to override the defaults */ - -/* Page background color */ -body { background-color: #FFFFFF; color:#000000 } - -/* Headings */ -h1 { font-size: 145% } - -/* Table colors */ -.TableHeadingColor { background: #CCCCFF; color:#000000 } /* Dark mauve */ -.TableSubHeadingColor { background: #EEEEFF; color:#000000 } /* Light mauve */ -.TableRowColor { background: #FFFFFF; color:#000000 } /* White */ - -/* Font used in left-hand frame lists */ -.FrameTitleFont { font-size: 100%; font-family: Helvetica, Arial, sans-serif; color:#000000 } -.FrameHeadingFont { font-size: 90%; font-family: Helvetica, Arial, sans-serif; color:#000000 } -.FrameItemFont { font-size: 90%; font-family: Helvetica, Arial, sans-serif; color:#000000 } - -/* Navigation bar fonts and colors */ -.NavBarCell1 { background-color:#EEEEFF; color:#000000} /* Light mauve */ -.NavBarCell1Rev { background-color:#00008B; color:#FFFFFF} /* Dark Blue */ -.NavBarFont1 { font-family: Arial, Helvetica, sans-serif; color:#000000;color:#000000;} -.NavBarFont1Rev { font-family: Arial, Helvetica, sans-serif; color:#FFFFFF;color:#FFFFFF;} - -.NavBarCell2 { font-family: Arial, Helvetica, sans-serif; background-color:#FFFFFF; color:#000000} -.NavBarCell3 { font-family: Arial, Helvetica, sans-serif; background-color:#FFFFFF; color:#000000} - diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 000000000..42d355a14 Binary files /dev/null and b/favicon.ico differ diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..86a27f74c --- /dev/null +++ b/gradle.properties @@ -0,0 +1,18 @@ +VERSION_NAME=4.2.2 +VERSION_CODE=17 +GROUP=com.jjoe64 + +POM_DESCRIPTION=Android Graph Library for creating zoomable and scrollable charts. +POM_URL=http://android-graphview.org/ +POM_SCM_URL=https://github.com/jjoe64/GraphView +POM_SCM_CONNECTION=scm:git@github.com:jjoe64/GraphView.git +POM_SCM_DEV_CONNECTION=scm:git@github.com:jjoe64/GraphView.git +POM_LICENCE_NAME=Apache License, Version 2.0 +POM_LICENCE_URL=https://github.com/jjoe64/GraphView/blob/master/license.txt +POM_LICENCE_DIST=repo +POM_DEVELOPER_ID=jjoe64 +POM_DEVELOPER_NAME=Jonas Gehring + +POM_NAME=GraphView +POM_ARTIFACT_ID=graphview +POM_PACKAGING=aar diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..01b8bf6b1 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..25f587d12 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..cccdd3d51 --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..e95643d6a --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/license.txt b/license.txt index 6f0be0f95..f92a0dc19 100644 --- a/license.txt +++ b/license.txt @@ -1,57 +1,68 @@ -GNU LESSER GENERAL PUBLIC LICENSE +Copyright 2016 Jonas Gehring -Version 3, 29 June 2007 +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 -Copyright © 2007 Free Software Foundation, Inc. + http://www.apache.org/licenses/LICENSE-2.0 -Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. +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. -This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. -0. Additional Definitions. -As used herein, “this License” refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL” refers to version 3 of the GNU General Public License. +Apache License -“The Library” refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. +Version 2.0, January 2004 -An “Application” is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. +http://www.apache.org/licenses/ -A “Combined Work” is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Version”. +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -The “Minimal Corresponding Source” for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. +1. Definitions. -The “Corresponding Application Code” for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. -1. Exception to Section 3 of the GNU GPL. -You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. -2. Conveying Modified Versions. -If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: +"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. -a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or -b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. -3. Object Code Incorporating Material from Library Header Files. -The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. -a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. -b) Accompany the object code with a copy of the GNU GPL and this license document. -4. Combined Works. -You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. -a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. -b) Accompany the Combined Work with a copy of the GNU GPL and this license document. -c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. -d) Do one of the following: -0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. -1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. -e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) -5. Combined Libraries. -You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: +"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. -a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. -b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. -6. Revised Versions of the GNU Lesser General Public License. -The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. +"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). -Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. +"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. -If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. \ No newline at end of file +"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: + +You must give any other recipients of the Work or Derivative Works a copy of this License; and +You must cause any modified files to carry prominent notices stating that You changed the files; and +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 +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 diff --git a/maven_push.gradle b/maven_push.gradle new file mode 100644 index 000000000..3d15ef167 --- /dev/null +++ b/maven_push.gradle @@ -0,0 +1,121 @@ +apply plugin: 'maven' +apply plugin: 'signing' + +import org.gradle.plugins.signing.Sign + +gradle.taskGraph.whenReady { taskGraph -> + if (taskGraph.allTasks.any { it instanceof Sign }) { + // Use Java 6's console to read from the console (no good for + // a CI environment) + Console console = System.console() + console.printf "\n\nWe have to sign some things in this build." + + "\n\nPlease enter your signing details.\n\n" + + def id = "D8C3B041" + def file = "/Users/jonas/.gnupg/secring.gpg" + def password = "" + + allprojects { ext."signing.keyId" = id } + allprojects { ext."signing.secretKeyRingFile" = file } + allprojects { ext."signing.password" = password } + + console.printf "\nThanks.\n\n" + file + } +} + +def sonatypeRepositoryUrl +if (isReleaseBuild()) { + println 'RELEASE BUILD' + sonatypeRepositoryUrl = hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL + : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" +} else { + println 'DEBUG BUILD' + sonatypeRepositoryUrl = hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL + : "https://oss.sonatype.org/content/repositories/snapshots/" +} + +def getRepositoryUsername() { + return "" +} + +def getRepositoryPassword() { + return "" +} + +def isReleaseBuild() { + return version.contains("SNAPSHOT") == false +} + +afterEvaluate { project -> + uploadArchives { + repositories { + mavenDeployer { + beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } + + pom.groupId = GROUP + pom.artifactId = POM_ARTIFACT_ID + pom.version = VERSION_NAME + + repository(url: sonatypeRepositoryUrl) { + authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) + } + + pom.project { + name POM_NAME + packaging POM_PACKAGING + description POM_DESCRIPTION + url POM_URL + + + scm { + url POM_SCM_URL + connection POM_SCM_CONNECTION + developerConnection POM_SCM_DEV_CONNECTION + } + + licenses { + license { + name POM_LICENCE_NAME + url POM_LICENCE_URL + distribution POM_LICENCE_DIST + } + } + + developers { + developer { + id POM_DEVELOPER_ID + name POM_DEVELOPER_NAME + } + } + } + } + } + } + + signing { + required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } + sign configurations.archives + } + + task androidJavadocs(type: Javadoc) { + source = android.sourceSets.main.java.sourceFiles + } + + task androidJavadocsJar(type: Jar) { + classifier = 'javadoc' + //basename = artifact_id + from androidJavadocs.destinationDir + } + + task androidSourcesJar(type: Jar) { + classifier = 'sources' + //basename = artifact_id + from android.sourceSets.main.java.sourceFiles + } + + artifacts { + //archives packageReleaseJar + archives androidSourcesJar + archives androidJavadocsJar + } +} diff --git a/proguard-rules.txt b/proguard-rules.txt new file mode 100644 index 000000000..f62b0eb4f --- /dev/null +++ b/proguard-rules.txt @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /home/jonas/android-studio/android-studio/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} \ No newline at end of file diff --git a/project.properties b/project.properties deleted file mode 100644 index 484dab075..000000000 --- a/project.properties +++ /dev/null @@ -1,15 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system edit -# "ant.properties", and override values to adapt the script to your -# project structure. -# -# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): -#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt - -# Project target. -target=android-17 -android.library=true diff --git a/public/GraphView-3.1.2.jar b/public/GraphView-3.1.2.jar new file mode 100644 index 000000000..978e47f9a Binary files /dev/null and b/public/GraphView-3.1.2.jar differ diff --git a/public/GraphView-3.1.3.jar b/public/GraphView-3.1.3.jar new file mode 100644 index 000000000..94289eeed Binary files /dev/null and b/public/GraphView-3.1.3.jar differ diff --git a/public/GraphView-3.1.4.jar b/public/GraphView-3.1.4.jar new file mode 100644 index 000000000..5b28c5d6a Binary files /dev/null and b/public/GraphView-3.1.4.jar differ diff --git a/public/GraphView-4.0.0.jar b/public/GraphView-4.0.0.jar new file mode 100644 index 000000000..28ec762a4 Binary files /dev/null and b/public/GraphView-4.0.0.jar differ diff --git a/public/GraphView-4.0.1.jar b/public/GraphView-4.0.1.jar new file mode 100644 index 000000000..693245bfb Binary files /dev/null and b/public/GraphView-4.0.1.jar differ diff --git a/public/GraphView-4.1.0.jar b/public/GraphView-4.1.0.jar new file mode 100644 index 000000000..1f6a7f72d Binary files /dev/null and b/public/GraphView-4.1.0.jar differ diff --git a/public/GraphView-4.1.1.jar b/public/GraphView-4.1.1.jar new file mode 100644 index 000000000..b42e0f33a Binary files /dev/null and b/public/GraphView-4.1.1.jar differ diff --git a/public/GraphView-4.2.0.jar b/public/GraphView-4.2.0.jar new file mode 100644 index 000000000..4da2fc27a Binary files /dev/null and b/public/GraphView-4.2.0.jar differ diff --git a/public/GraphView-4.2.1.jar b/public/GraphView-4.2.1.jar new file mode 100644 index 000000000..c45cdd1b8 Binary files /dev/null and b/public/GraphView-4.2.1.jar differ diff --git a/src/com/jjoe64/graphview/BarGraphView.java b/src/com/jjoe64/graphview/BarGraphView.java deleted file mode 100644 index e1656710d..000000000 --- a/src/com/jjoe64/graphview/BarGraphView.java +++ /dev/null @@ -1,129 +0,0 @@ -/** - * This file is part of GraphView. - * - * GraphView is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * GraphView is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GraphView. If not, see . - * - * Copyright Jonas Gehring - */ - -package com.jjoe64.graphview; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint.Align; -import android.util.AttributeSet; - -import com.jjoe64.graphview.GraphViewSeries.GraphViewSeriesStyle; - -/** - * Draws a Bar Chart - * @author Muhammad Shahab Hameed - */ -public class BarGraphView extends GraphView { - private boolean drawValuesOnTop; - private int valuesOnTopColor = Color.WHITE; - - public BarGraphView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public BarGraphView(Context context, String title) { - super(context, title); - } - - @Override - protected void drawHorizontalLabels(Canvas canvas, float border, - float horstart, float height, String[] horlabels, float graphwidth) { - // horizontal labels + lines - paint.setTextAlign(Align.CENTER); - - int hors = horlabels.length; - float barwidth = graphwidth/horlabels.length; - float textOffset = barwidth/2; - for (int i = 0; i < horlabels.length; i++) { - // lines - float x = ((graphwidth / hors) * i) + horstart; - paint.setColor(graphViewStyle.getGridColor()); - canvas.drawLine(x, height - border, x, border, paint); - - if(getShowHorizontalLabels()) { - // text - x = barwidth*i + textOffset + horstart; - paint.setColor(graphViewStyle.getHorizontalLabelsColor()); - canvas.drawText(horlabels[i], x, height - 4, paint); - } - } - } - - @SuppressWarnings("deprecation") - @Override - public void drawSeries(Canvas canvas, GraphViewDataInterface[] values, float graphwidth, float graphheight, - float border, double minX, double minY, double diffX, double diffY, - float horstart, GraphViewSeriesStyle style) { - float colwidth = graphwidth / (values.length); - - paint.setStrokeWidth(style.thickness); - - float offset = 0; - - // draw data - for (int i = 0; i < values.length; i++) { - float valY = (float) (values[i].getY() - minY); - float ratY = (float) (valY / diffY); - float y = graphheight * ratY; - - // hook for value dependent color - if (style.getValueDependentColor() != null) { - paint.setColor(style.getValueDependentColor().get(values[i])); - } else { - paint.setColor(style.color); - } - - float left = (i * colwidth) + horstart -offset; - float top = (border - y) + graphheight; - float right = ((i * colwidth) + horstart) + (colwidth - 1) -offset; - canvas.drawRect(left, top, right, graphheight + border - 1, paint); - - // -----Set values on top of graph--------- - if (drawValuesOnTop) { - top -= 4; - if (top<=border) top+=border+4; - paint.setTextAlign(Align.CENTER); - paint.setColor(valuesOnTopColor ); - canvas.drawText(formatLabel(values[i].getY(), false), (left+right)/2, top, paint); - } - } - } - - public boolean getDrawValuesOnTop() { - return drawValuesOnTop; - } - - public int getValuesOnTopColor() { - return valuesOnTopColor; - } - - /** - * You can set the flag to let the GraphView draw the values on top of the bars - * @param drawValuesOnTop - */ - public void setDrawValuesOnTop(boolean drawValuesOnTop) { - this.drawValuesOnTop = drawValuesOnTop; - } - - public void setValuesOnTopColor(int valuesOnTopColor) { - this.valuesOnTopColor = valuesOnTopColor; - } -} diff --git a/src/com/jjoe64/graphview/CustomLabelFormatter.java b/src/com/jjoe64/graphview/CustomLabelFormatter.java deleted file mode 100644 index dcb266a6a..000000000 --- a/src/com/jjoe64/graphview/CustomLabelFormatter.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * This file is part of GraphView. - * - * GraphView is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * GraphView is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GraphView. If not, see . - * - * Copyright Jonas Gehring - */ - -package com.jjoe64.graphview; - -/** - * if you want to show different labels, - * you can use this label formatter. - * As Input you get the raw value (x or y) and - * you return a String that will be displayed. - * {@code - * graphView.setCustomLabelFormatter(new CustomLabelFormatter() { - public String formatLabel(double value, boolean isValueX) { - if (isValueX) { - if (value < 5) { - return "small"; - } else if (value < 15) { - return "middle"; - } else { - return "big"; - } - } - return null; // let graphview generate Y-axis label for us - } - }); - * } - */ -public interface CustomLabelFormatter { - - /** - * will be called when the labels were generated - * @param value the raw input value (x or y) - * @param isValueX true if value is a x-value, false if otherwise - * @return the string that will be displayed. return null if you want graphview to generate the label for you. - */ - String formatLabel(double value, boolean isValueX); - -} diff --git a/src/com/jjoe64/graphview/GraphView.java b/src/com/jjoe64/graphview/GraphView.java deleted file mode 100755 index 6a1060f95..000000000 --- a/src/com/jjoe64/graphview/GraphView.java +++ /dev/null @@ -1,1028 +0,0 @@ -/** - * This file is part of GraphView. - * - * GraphView is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * GraphView is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GraphView. If not, see . - * - * Copyright Jonas Gehring - */ - -package com.jjoe64.graphview; - -import java.text.NumberFormat; -import java.util.ArrayList; -import java.util.List; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Paint.Align; -import android.graphics.Rect; -import android.graphics.RectF; -import android.util.AttributeSet; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.widget.LinearLayout; - -import com.jjoe64.graphview.GraphViewSeries.GraphViewSeriesStyle; -import com.jjoe64.graphview.GraphViewStyle.GridStyle; -import com.jjoe64.graphview.compatible.ScaleGestureDetector; - -/** - * GraphView is a Android View for creating zoomable and scrollable graphs. - * This is the abstract base class for all graphs. Extend this class and implement {@link #drawSeries(Canvas, GraphViewDataInterface[], float, float, float, double, double, double, double, float)} to display a custom graph. - * Use {@link LineGraphView} for creating a line chart. - * - * @author jjoe64 - jonas gehring - http://www.jjoe64.com - * - * Copyright (C) 2011 Jonas Gehring - * Licensed under the GNU Lesser General Public License (LGPL) - * http://www.gnu.org/licenses/lgpl.html - */ -abstract public class GraphView extends LinearLayout { - static final private class GraphViewConfig { - static final float BORDER = 20; - } - - private class GraphViewContentView extends View { - private float lastTouchEventX; - private float graphwidth; - private boolean scrollingStarted; - - /** - * @param context - */ - public GraphViewContentView(Context context) { - super(context); - setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); - } - - /** - * @param canvas - */ - @Override - protected void onDraw(Canvas canvas) { - - paint.setAntiAlias(true); - - // normal - paint.setStrokeWidth(0); - - float border = GraphViewConfig.BORDER; - float horstart = 0; - float height = getHeight(); - float width = getWidth() - 1; - double maxY = getMaxY(); - double minY = getMinY(); - double maxX = getMaxX(false); - double minX = getMinX(false); - double diffX = maxX - minX; - - // measure bottom text - if (labelTextHeight == null || horLabelTextWidth == null) { - paint.setTextSize(getGraphViewStyle().getTextSize()); - double testX = ((getMaxX(true)-getMinX(true))*0.783)+getMinX(true); - String testLabel = formatLabel(testX, true); - paint.getTextBounds(testLabel, 0, testLabel.length(), textBounds); - labelTextHeight = (textBounds.height()); - horLabelTextWidth = (textBounds.width()); - } - border += labelTextHeight; - - float graphheight = height - (2 * border); - graphwidth = width; - - if (horlabels == null) { - horlabels = generateHorlabels(graphwidth); - } - if (verlabels == null) { - verlabels = generateVerlabels(graphheight); - } - - // vertical lines - if(graphViewStyle.getGridStyle() != GridStyle.HORIZONTAL) { - paint.setTextAlign(Align.LEFT); - int vers = verlabels.length - 1; - for (int i = 0; i < verlabels.length; i++) { - paint.setColor(graphViewStyle.getGridColor()); - float y = ((graphheight / vers) * i) + border; - canvas.drawLine(horstart, y, width, y, paint); - } - } - - drawHorizontalLabels(canvas, border, horstart, height, horlabels, graphwidth); - - paint.setColor(graphViewStyle.getHorizontalLabelsColor()); - paint.setTextAlign(Align.CENTER); - canvas.drawText(title, (graphwidth / 2) + horstart, border - 4, paint); - - if (maxY == minY) { - // if min/max is the same, fake it so that we can render a line - if(maxY == 0) { - // if both are zero, change the values to prevent division by zero - maxY = 1.0d; - minY = 0.0d; - } else { - maxY = maxY*1.05d; - minY = minY*0.95d; - } - } - - double diffY = maxY - minY; - paint.setStrokeCap(Paint.Cap.ROUND); - - for (int i=0; i maxX) { - viewportStart = maxX - viewportSize; - } - - // labels have to be regenerated - if (!staticHorizontalLabels) horlabels = null; - if (!staticVerticalLabels) verlabels = null; - viewVerLabels.invalidate(); - } - invalidate(); - } - - /** - * @param event - */ - @Override - public boolean onTouchEvent(MotionEvent event) { - if (!isScrollable() || isDisableTouch()) { - return super.onTouchEvent(event); - } - - boolean handled = false; - // first scale - if (scalable && scaleDetector != null) { - scaleDetector.onTouchEvent(event); - handled = scaleDetector.isInProgress(); - } - if (!handled) { - //Log.d("GraphView", "on touch event scale not handled+"+lastTouchEventX); - // if not scaled, scroll - if ((event.getAction() & MotionEvent.ACTION_DOWN) == MotionEvent.ACTION_DOWN && - (event.getAction() & MotionEvent.ACTION_MOVE) == 0) { - scrollingStarted = true; - handled = true; - } - if ((event.getAction() & MotionEvent.ACTION_UP) == MotionEvent.ACTION_UP) { - scrollingStarted = false; - lastTouchEventX = 0; - handled = true; - } - if ((event.getAction() & MotionEvent.ACTION_MOVE) == MotionEvent.ACTION_MOVE) { - if (scrollingStarted) { - if (lastTouchEventX != 0) { - onMoveGesture(event.getX() - lastTouchEventX); - } - lastTouchEventX = event.getX(); - handled = true; - } - } - if (handled) - invalidate(); - } else { - // currently scaling - scrollingStarted = false; - lastTouchEventX = 0; - } - return handled; - } - } - - /** - * one data set for a graph series - */ - static public class GraphViewData implements GraphViewDataInterface { - public final double valueX; - public final double valueY; - public GraphViewData(double valueX, double valueY) { - super(); - this.valueX = valueX; - this.valueY = valueY; - } - @Override - public double getX() { - return valueX; - } - @Override - public double getY() { - return valueY; - } - } - - public enum LegendAlign { - TOP, MIDDLE, BOTTOM - } - - private class VerLabelsView extends View { - /** - * @param context - */ - public VerLabelsView(Context context) { - super(context); - setLayoutParams(new LayoutParams( - getGraphViewStyle().getVerticalLabelsWidth()==0?100:getGraphViewStyle().getVerticalLabelsWidth() - , LayoutParams.FILL_PARENT)); - } - - /** - * @param canvas - */ - @Override - protected void onDraw(Canvas canvas) { - // normal - paint.setStrokeWidth(0); - - // measure bottom text - if (labelTextHeight == null || verLabelTextWidth == null) { - paint.setTextSize(getGraphViewStyle().getTextSize()); - double testY = ((getMaxY()-getMinY())*0.783)+getMinY(); - String testLabel = formatLabel(testY, false); - paint.getTextBounds(testLabel, 0, testLabel.length(), textBounds); - labelTextHeight = (textBounds.height()); - verLabelTextWidth = (textBounds.width()); - } - if (getGraphViewStyle().getVerticalLabelsWidth()==0 && getLayoutParams().width != verLabelTextWidth+GraphViewConfig.BORDER) { - setLayoutParams(new LayoutParams( - (int) (verLabelTextWidth+GraphViewConfig.BORDER), LayoutParams.FILL_PARENT)); - } else if (getGraphViewStyle().getVerticalLabelsWidth()!=0 && getGraphViewStyle().getVerticalLabelsWidth() != getLayoutParams().width) { - setLayoutParams(new LayoutParams( - getGraphViewStyle().getVerticalLabelsWidth(), LayoutParams.FILL_PARENT)); - } - - float border = GraphViewConfig.BORDER; - border += labelTextHeight; - float height = getHeight(); - float graphheight = height - (2 * border); - - if (verlabels == null) { - verlabels = generateVerlabels(graphheight); - } - - // vertical labels - paint.setTextAlign(getGraphViewStyle().getVerticalLabelsAlign()); - int labelsWidth = getWidth(); - int labelsOffset = 0; - if (getGraphViewStyle().getVerticalLabelsAlign() == Align.RIGHT) { - labelsOffset = labelsWidth; - } else if (getGraphViewStyle().getVerticalLabelsAlign() == Align.CENTER) { - labelsOffset = labelsWidth / 2; - } - int vers = verlabels.length - 1; - for (int i = 0; i < verlabels.length; i++) { - float y = ((graphheight / vers) * i) + border; - paint.setColor(graphViewStyle.getVerticalLabelsColor()); - canvas.drawText(verlabels[i], labelsOffset, y, paint); - } - - // reset - paint.setTextAlign(Align.LEFT); - } - } - - protected final Paint paint; - private String[] horlabels; - private String[] verlabels; - private String title; - private boolean scrollable; - private boolean disableTouch; - private double viewportStart; - private double viewportSize; - private final View viewVerLabels; - private ScaleGestureDetector scaleDetector; - private boolean scalable; - private final NumberFormat[] numberformatter = new NumberFormat[2]; - private final List graphSeries; - private boolean showLegend = false; - private LegendAlign legendAlign = LegendAlign.MIDDLE; - private boolean manualYAxis; - private boolean manualMaxY; - private boolean manualMinY; - private double manualMaxYValue; - private double manualMinYValue; - protected GraphViewStyle graphViewStyle; - private final GraphViewContentView graphViewContentView; - private CustomLabelFormatter customLabelFormatter; - private Integer labelTextHeight; - private Integer horLabelTextWidth; - private Integer verLabelTextWidth; - private final Rect textBounds = new Rect(); - private boolean staticHorizontalLabels; - private boolean staticVerticalLabels; - private boolean showHorizontalLabels = true; - private boolean showVerticalLabels = true; - - public GraphView(Context context, AttributeSet attrs) { - this(context, attrs.getAttributeValue(null, "title")); - - int width = attrs.getAttributeIntValue("android", "layout_width", LayoutParams.MATCH_PARENT); - int height = attrs.getAttributeIntValue("android", "layout_height", LayoutParams.MATCH_PARENT); - setLayoutParams(new LayoutParams(width, height)); - } - - /** - * @param context - * @param title [optional] - */ - public GraphView(Context context, String title) { - super(context); - setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); - - if (title == null) - this.title = ""; - else - this.title = title; - - graphViewStyle = new GraphViewStyle(); - graphViewStyle.useTextColorFromTheme(context); - - paint = new Paint(); - graphSeries = new ArrayList(); - - viewVerLabels = new VerLabelsView(context); - addView(viewVerLabels); - graphViewContentView = new GraphViewContentView(context); - addView(graphViewContentView, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT, 1)); - } - - private GraphViewDataInterface[] _values(int idxSeries) { - GraphViewDataInterface[] values = graphSeries.get(idxSeries).values; - synchronized (values) { - if (viewportStart == 0 && viewportSize == 0) { - // all data - return values; - } else { - // viewport - List listData = new ArrayList(); - for (int i=0; i= viewportStart) { - if (values[i].getX() > viewportStart+viewportSize) { - listData.add(values[i]); // one more for nice scrolling - break; - } else { - listData.add(values[i]); - } - } else { - if (listData.isEmpty()) { - listData.add(values[i]); - } - listData.set(0, values[i]); // one before, for nice scrolling - } - } - return listData.toArray(new GraphViewDataInterface[listData.size()]); - } - } - } - - /** - * add a series of data to the graph - * @param series - */ - public void addSeries(GraphViewSeries series) { - series.addGraphView(this); - graphSeries.add(series); - redrawAll(); - } - - protected void drawHorizontalLabels(Canvas canvas, float border, - float horstart, float height, String[] horlabels, float graphwidth) { - // horizontal labels + lines - int hors = horlabels.length - 1; - for (int i = 0; i < horlabels.length; i++) { - paint.setColor(graphViewStyle.getGridColor()); - float x = ((graphwidth / hors) * i) + horstart; - if(graphViewStyle.getGridStyle() != GridStyle.VERTICAL) { - canvas.drawLine(x, height - border, x, border, paint); - } - if(showHorizontalLabels) { - paint.setTextAlign(Align.CENTER); - if (i==horlabels.length-1) - paint.setTextAlign(Align.RIGHT); - if (i==0) - paint.setTextAlign(Align.LEFT); - paint.setColor(graphViewStyle.getHorizontalLabelsColor()); - canvas.drawText(horlabels[i], x, height - 4, paint); - } - } - } - - protected void drawLegend(Canvas canvas, float height, float width) { - float textSize = paint.getTextSize(); - int spacing = getGraphViewStyle().getLegendSpacing(); - int border = getGraphViewStyle().getLegendBorder(); - int legendWidth = getGraphViewStyle().getLegendWidth(); - - int shapeSize = (int) (textSize*0.8d); - Log.d("GraphView", "draw legend size: "+paint.getTextSize()); - - // rect - paint.setARGB(180, 100, 100, 100); - float legendHeight = (shapeSize+spacing)*graphSeries.size() +2*border -spacing; - float lLeft = width-legendWidth - border*2; - float lTop; - switch (legendAlign) { - case TOP: - lTop = 0; - break; - case MIDDLE: - lTop = height/2 - legendHeight/2; - break; - default: - lTop = height - GraphViewConfig.BORDER - legendHeight - getGraphViewStyle().getLegendMarginBottom(); - } - float lRight = lLeft+legendWidth; - float lBottom = lTop+legendHeight; - canvas.drawRoundRect(new RectF(lLeft, lTop, lRight, lBottom), 8, 8, paint); - - for (int i=0; i 0) { - GraphViewDataInterface[] values = graphSeries.get(0).values; - if (values.length == 0) { - highest = 0; - } else { - highest = values[values.length-1].getX(); - } - for (int i=1; i 0) { - highest = Math.max(highest, values[values.length-1].getX()); - } - } - } - return highest; - } - } - - /** - * returns the maximal Y value of all data. - * - * warning: only override this, if you really know want you're doing! - */ - protected double getMaxY() { - double largest; - if (manualYAxis || manualMaxY) { - largest = manualMaxYValue; - } else { - largest = Integer.MIN_VALUE; - for (int i=0; i largest) - largest = values[ii].getY(); - } - } - return largest; - } - - /** - * returns the minimal X value of the current viewport (if viewport is set) - * otherwise minimal X value of all data. - * @param ignoreViewport - * - * warning: only override this, if you really know want you're doing! - */ - protected double getMinX(boolean ignoreViewport) { - // if viewport is set, use this - if (!ignoreViewport && viewportSize != 0) { - return viewportStart; - } else { - // otherwise use the min x value - // values must be sorted by x, so the first value has the smallest X value - double lowest = 0; - if (graphSeries.size() > 0) { - GraphViewDataInterface[] values = graphSeries.get(0).values; - if (values.length == 0) { - lowest = 0; - } else { - lowest = values[0].getX(); - } - for (int i=1; i 0) { - lowest = Math.min(lowest, values[0].getX()); - } - } - } - return lowest; - } - } - - /** - * returns the minimal Y value of all data. - * - * warning: only override this, if you really know want you're doing! - */ - protected double getMinY() { - double smallest; - if (manualYAxis || manualMinY) { - smallest = manualMinYValue; - } else { - smallest = Integer.MAX_VALUE; - for (int i=0; i= graphSeries.size()) { - throw new IndexOutOfBoundsException("No series at index " + index); - } - - removeSeries(graphSeries.get(index)); - } - - /** - * scrolls to the last x-value - * @throws IllegalStateException if scrollable == false - */ - public void scrollToEnd() { - if (!scrollable) throw new IllegalStateException("This GraphView is not scrollable."); - double max = getMaxX(true); - viewportStart = max-viewportSize; - - // don't clear labels width/height cache - // so that the display is not flickering - if (!staticVerticalLabels) verlabels = null; - if (!staticHorizontalLabels) horlabels = null; - - invalidate(); - viewVerLabels.invalidate(); - graphViewContentView.invalidate(); - } - - /** - * set a custom label formatter - * @param customLabelFormatter - */ - public void setCustomLabelFormatter(CustomLabelFormatter customLabelFormatter) { - this.customLabelFormatter = customLabelFormatter; - } - - /** - * The user can disable any touch gestures, this is useful if you are using a real time graph, but don't want the user to interact - * @param disableTouch - */ - public void setDisableTouch(boolean disableTouch) { - this.disableTouch = disableTouch; - } - - /** - * set custom graphview style - * @param style - */ - public void setGraphViewStyle(GraphViewStyle style) { - graphViewStyle = style; - labelTextHeight = null; - } - - /** - * set's static horizontal labels (from left to right) - * @param horlabels if null, labels were generated automatically - */ - public void setHorizontalLabels(String[] horlabels) { - staticHorizontalLabels = horlabels != null; - this.horlabels = horlabels; - } - - /** - * legend position - * @param legendAlign - */ - public void setLegendAlign(LegendAlign legendAlign) { - this.legendAlign = legendAlign; - } - - /** - * legend width - * @param legendWidth - * @deprecated use {@link GraphViewStyle#setLegendWidth(int)} - */ - @Deprecated - public void setLegendWidth(float legendWidth) { - getGraphViewStyle().setLegendWidth((int)legendWidth); - } - - /** - * you have to set the bounds {@link #setManualYAxisBounds(double, double)}. That automatically enables manualYAxis-flag. - * if you want to disable the menual y axis, call this method with false. - * @param manualYAxis - */ - public void setManualYAxis(boolean manualYAxis) { - this.manualYAxis = manualYAxis; - } - - /** - * if you want to disable the menual y axis maximum bound, call this method with false. - */ - public void setManualMaxY(boolean manualMaxY) { - this.manualMaxY = manualMaxY; - } - - /** - * if you want to disable the menual y axis minimum bound, call this method with false. - */ - public void setManualMinY(boolean manualMinY) { - this.manualMinY = manualMinY; - } - - /** - * set manual Y axis limit - * @param max - * @param min - */ - public void setManualYAxisBounds(double max, double min) { - manualMaxYValue = max; - manualMinYValue = min; - manualYAxis = true; - } - - /* - * set manual Y axis max limit - * @param max - */ - public void setManualYMaxBound(double max) { - manualMaxYValue = max; - manualMaxY = true; - } - - /* - * set manual Y axis min limit - * @param min - */ - public void setManualYMinBound(double min) { - manualMinYValue = min; - manualMinY = true; - } - - /** - * this forces scrollable = true - * @param scalable - */ - synchronized public void setScalable(boolean scalable) { - this.scalable = scalable; - if (scalable == true && scaleDetector == null) { - scrollable = true; // automatically forces this - scaleDetector = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.SimpleOnScaleGestureListener() { - @Override - public boolean onScale(ScaleGestureDetector detector) { - double center = viewportStart + viewportSize / 2; - viewportSize /= detector.getScaleFactor(); - viewportStart = center - viewportSize / 2; - - // viewportStart must not be < minX - double minX = getMinX(true); - if (viewportStart < minX) { - viewportStart = minX; - } - - // viewportStart + viewportSize must not be > maxX - double maxX = getMaxX(true); - if (viewportSize == 0) { - viewportSize = maxX; - } - double overlap = viewportStart + viewportSize - maxX; - if (overlap > 0) { - // scroll left - if (viewportStart-overlap > minX) { - viewportStart -= overlap; - } else { - // maximal scale - viewportStart = minX; - viewportSize = maxX - viewportStart; - } - } - redrawAll(); - return true; - } - }); - } - } - - /** - * the user can scroll (horizontal) the graph. This is only useful if you use a viewport {@link #setViewPort(double, double)} which doesn't displays all data. - * @param scrollable - */ - public void setScrollable(boolean scrollable) { - this.scrollable = scrollable; - } - - public void setShowLegend(boolean showLegend) { - this.showLegend = showLegend; - } - - /** - * sets the title of graphview - * @param title - */ - public void setTitle(String title) { - this.title = title; - } - - /** - * set's static vertical labels (from top to bottom) - * @param verlabels if null, labels were generated automatically - */ - public void setVerticalLabels(String[] verlabels) { - staticVerticalLabels = verlabels != null; - this.verlabels = verlabels; - } - - /** - * set's the viewport for the graph. - * @see #setManualYAxisBounds(double, double) to limit the y-viewport - * @param start x-value - * @param size - */ - public void setViewPort(double start, double size) { - if (size<0) { - throw new IllegalArgumentException("Viewport size must be greater than 0!"); - } - viewportStart = start; - viewportSize = size; - } - - /** - * Sets whether horizontal labels are drawn or not. - * - * @param showHorizontalLabels - */ - public void setShowHorizontalLabels(boolean showHorizontalLabels) { - this.showHorizontalLabels = showHorizontalLabels; - redrawAll(); - } - - /** - * Gets are horizontal labels drawn. - * - * @return {@code True} if horizontal labels are drawn - */ - public boolean getShowHorizontalLabels() { - return showHorizontalLabels; - } - - /** - * Sets whether vertical labels are drawn or not. - * - * @param showVerticalLabels - */ - public void setShowVerticalLabels(boolean showVerticalLabels) { - this.showVerticalLabels = showVerticalLabels; - if(this.showVerticalLabels) { - addView(viewVerLabels, 0); - } else { - removeView(viewVerLabels); - } - } - - /** - * Gets are vertical labels are drawn. - * - * @return {@code True} if vertical labels are drawn - */ - public boolean getShowVerticalLabels() { - return showVerticalLabels; - } - -} diff --git a/src/com/jjoe64/graphview/GraphViewDataInterface.java b/src/com/jjoe64/graphview/GraphViewDataInterface.java deleted file mode 100644 index e24638c44..000000000 --- a/src/com/jjoe64/graphview/GraphViewDataInterface.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * This file is part of GraphView. - * - * GraphView is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * GraphView is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GraphView. If not, see . - * - * Copyright Jonas Gehring - */ - -package com.jjoe64.graphview; - -/** - * the base interface for the graphview data. - * you can use your own data models, when they implement - * this interface. - */ -public interface GraphViewDataInterface { - public double getX(); - public double getY(); -} diff --git a/src/com/jjoe64/graphview/GraphViewSeries.java b/src/com/jjoe64/graphview/GraphViewSeries.java deleted file mode 100644 index a586fd044..000000000 --- a/src/com/jjoe64/graphview/GraphViewSeries.java +++ /dev/null @@ -1,171 +0,0 @@ -/** - * This file is part of GraphView. - * - * GraphView is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * GraphView is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GraphView. If not, see . - * - * Copyright Jonas Gehring - */ - -package com.jjoe64.graphview; - -import java.util.ArrayList; -import java.util.List; - -/** - * a graphview series. - * holds the data, description and styles - */ -public class GraphViewSeries { - /** - * graph series style: color and thickness - */ - static public class GraphViewSeriesStyle { - public int color = 0xff0077cc; - public int thickness = 3; - private ValueDependentColor valueDependentColor; - - public GraphViewSeriesStyle() { - super(); - } - public GraphViewSeriesStyle(int color, int thickness) { - super(); - this.color = color; - this.thickness = thickness; - } - - public ValueDependentColor getValueDependentColor() { - return valueDependentColor; - } - - /** - * the color depends on the value of the data. - * only possible in BarGraphView - * @param valueDependentColor - */ - public void setValueDependentColor(ValueDependentColor valueDependentColor) { - this.valueDependentColor = valueDependentColor; - } - } - - final String description; - final GraphViewSeriesStyle style; - GraphViewDataInterface[] values; - private final List graphViews = new ArrayList(); - - public GraphViewSeries(GraphViewDataInterface[] values) { - description = null; - style = new GraphViewSeriesStyle(); - this.values = values; - } - - public GraphViewSeries(String description, GraphViewSeriesStyle style, GraphViewDataInterface[] values) { - super(); - this.description = description; - if (style == null) { - style = new GraphViewSeriesStyle(); - } - this.style = style; - this.values = values; - } - - /** - * this graphview will be redrawn if data changes - * @param graphView - */ - public void addGraphView(GraphView graphView) { - this.graphViews.add(graphView); - } - - /** - * add one data to current data - * @param value the new data to append - * @param scrollToEnd true => graphview will scroll to the end (maxX) - * @deprecated please use {@link #appendData(GraphViewDataInterface, boolean, int)} to avoid memory overflow - */ - @Deprecated - public void appendData(GraphViewDataInterface value, boolean scrollToEnd) { - GraphViewDataInterface[] newValues = new GraphViewDataInterface[values.length + 1]; - int offset = values.length; - System.arraycopy(values, 0, newValues, 0, offset); - - newValues[values.length] = value; - values = newValues; - for (GraphView g : graphViews) { - if (scrollToEnd) { - g.scrollToEnd(); - } - } - } - - /** - * add one data to current data - * @param value the new data to append - * @param scrollToEnd true => graphview will scroll to the end (maxX) - * @param maxDataCount if max data count is reached, the oldest data value will be lost - */ - public void appendData(GraphViewDataInterface value, boolean scrollToEnd, int maxDataCount) { - synchronized (values) { - int curDataCount = values.length; - GraphViewDataInterface[] newValues; - if (curDataCount < maxDataCount) { - // enough space - newValues = new GraphViewDataInterface[curDataCount + 1]; - System.arraycopy(values, 0, newValues, 0, curDataCount); - // append new data - newValues[curDataCount] = value; - } else { - // we have to trim one data - newValues = new GraphViewDataInterface[maxDataCount]; - System.arraycopy(values, 1, newValues, 0, curDataCount-1); - // append new data - newValues[maxDataCount-1] = value; - } - values = newValues; - } - - // update linked graph views - for (GraphView g : graphViews) { - if (scrollToEnd) { - g.scrollToEnd(); - } - } - } - - /** - * @return series styles. never null - */ - public GraphViewSeriesStyle getStyle() { - return style; - } - - /** - * you should use {@link GraphView#removeSeries(GraphViewSeries)} - * @param graphView - */ - public void removeGraphView(GraphView graphView) { - graphViews.remove(graphView); - } - - /** - * clears the current data and set the new. - * redraws the graphview(s) - * @param values new data - */ - public void resetData(GraphViewDataInterface[] values) { - this.values = values; - for (GraphView g : graphViews) { - g.redrawAll(); - } - } -} diff --git a/src/com/jjoe64/graphview/GraphViewStyle.java b/src/com/jjoe64/graphview/GraphViewStyle.java deleted file mode 100644 index 11d1da55d..000000000 --- a/src/com/jjoe64/graphview/GraphViewStyle.java +++ /dev/null @@ -1,202 +0,0 @@ -/** - * This file is part of GraphView. - * - * GraphView is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * GraphView is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GraphView. If not, see . - * - * Copyright Jonas Gehring - */ - -package com.jjoe64.graphview; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.graphics.Paint.Align; -import android.view.ContextThemeWrapper; - -/** - * Styles for the GraphView - * Important: Use GraphViewSeries.GraphViewSeriesStyle for series-specify styling - * - */ -public class GraphViewStyle { - private int verticalLabelsColor; - private int horizontalLabelsColor; - private int gridColor; - private GridStyle gridStyle = GridStyle.BOTH; - private float textSize; - private int verticalLabelsWidth; - private int numVerticalLabels; - private int numHorizontalLabels; - private int legendWidth; - private int legendBorder; - private int legendSpacing; - private int legendMarginBottom; - private Align verticalLabelsAlign; - - public GraphViewStyle() { - setDefaults(); - } - - public GraphViewStyle(int vLabelsColor, int hLabelsColor, int gridColor) { - setDefaults(); - this.verticalLabelsColor = vLabelsColor; - this.horizontalLabelsColor = hLabelsColor; - this.gridColor = gridColor; - } - - public int getGridColor() { - return gridColor; - } - - public GridStyle getGridStyle() { - return gridStyle; - } - - public int getHorizontalLabelsColor() { - return horizontalLabelsColor; - } - - public int getLegendBorder() { - return legendBorder; - } - - public int getLegendSpacing() { - return legendSpacing; - } - - public int getLegendWidth() { - return legendWidth; - } - - public int getLegendMarginBottom() { - return legendMarginBottom; - } - - public int getNumHorizontalLabels() { - return numHorizontalLabels; - } - - public int getNumVerticalLabels() { - return numVerticalLabels; - } - - public float getTextSize() { - return textSize; - } - - public Align getVerticalLabelsAlign() { - return verticalLabelsAlign; - } - - public int getVerticalLabelsColor() { - return verticalLabelsColor; - } - - public int getVerticalLabelsWidth() { - return verticalLabelsWidth; - } - - private void setDefaults() { - verticalLabelsColor = Color.WHITE; - horizontalLabelsColor = Color.WHITE; - gridColor = Color.DKGRAY; - textSize = 30f; - legendWidth = 120; - legendBorder = 10; - legendSpacing = 10; - legendMarginBottom = 0; - verticalLabelsAlign = Align.LEFT; - } - - public void setGridStyle(GridStyle style) { - gridStyle = style; - } - - public void setGridColor(int c) { - gridColor = c; - } - - public void setHorizontalLabelsColor(int c) { - horizontalLabelsColor = c; - } - - public void setLegendBorder(int legendBorder) { - this.legendBorder = legendBorder; - } - - public void setLegendSpacing(int legendSpacing) { - this.legendSpacing = legendSpacing; - } - - public void setLegendWidth(int legendWidth) { - this.legendWidth = legendWidth; - } - - public void setLegendMarginBottom(int legendMarginBottom) { - this.legendMarginBottom = legendMarginBottom; - } - - /** - * @param numHorizontalLabels 0 = auto - */ - public void setNumHorizontalLabels(int numHorizontalLabels) { - this.numHorizontalLabels = numHorizontalLabels; - } - - /** - * @param numVerticalLabels 0 = auto - */ - public void setNumVerticalLabels(int numVerticalLabels) { - this.numVerticalLabels = numVerticalLabels; - } - - public void setTextSize(float textSize) { - this.textSize = textSize; - } - - public void setVerticalLabelsAlign(Align verticalLabelsAlign) { - this.verticalLabelsAlign = verticalLabelsAlign; - } - - public void setVerticalLabelsColor(int c) { - verticalLabelsColor = c; - } - - /** - * @param verticalLabelsWidth 0 = auto - */ - public void setVerticalLabelsWidth(int verticalLabelsWidth) { - this.verticalLabelsWidth = verticalLabelsWidth; - } - - /** - * tries to get the theme's font color and use it for labels - * @param context must be instance of ContextThemeWrapper - */ - public void useTextColorFromTheme(Context context) { - if (context instanceof ContextThemeWrapper) { - TypedArray array = ((ContextThemeWrapper) context).getTheme().obtainStyledAttributes(new int[] {android.R.attr.textColorPrimary}); - int color = array.getColor(0, getVerticalLabelsColor()); - array.recycle(); - - setVerticalLabelsColor(color); - setHorizontalLabelsColor(color); - } - } - - public enum GridStyle { - BOTH, VERTICAL, HORIZONTAL - } -} diff --git a/src/com/jjoe64/graphview/LineGraphView.java b/src/com/jjoe64/graphview/LineGraphView.java deleted file mode 100644 index 1fce017f6..000000000 --- a/src/com/jjoe64/graphview/LineGraphView.java +++ /dev/null @@ -1,177 +0,0 @@ -/** - * This file is part of GraphView. - * - * GraphView is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * GraphView is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GraphView. If not, see . - * - * Copyright Jonas Gehring - */ - -package com.jjoe64.graphview; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Path; -import android.util.AttributeSet; - -import com.jjoe64.graphview.GraphViewSeries.GraphViewSeriesStyle; - -/** - * Line Graph View. This draws a line chart. - */ -public class LineGraphView extends GraphView { - private final Paint paintBackground; - private boolean drawBackground; - private boolean drawDataPoints; - private float dataPointsRadius = 10f; - - public LineGraphView(Context context, AttributeSet attrs) { - super(context, attrs); - - paintBackground = new Paint(); - paintBackground.setColor(Color.rgb(20, 40, 60)); - paintBackground.setStrokeWidth(4); - paintBackground.setAlpha(128); - } - - public LineGraphView(Context context, String title) { - super(context, title); - - paintBackground = new Paint(); - paintBackground.setColor(Color.rgb(20, 40, 60)); - paintBackground.setStrokeWidth(4); - paintBackground.setAlpha(128); - } - - @Override - public void drawSeries(Canvas canvas, GraphViewDataInterface[] values, float graphwidth, float graphheight, float border, double minX, double minY, double diffX, double diffY, float horstart, GraphViewSeriesStyle style) { - // draw background - double lastEndY = 0; - double lastEndX = 0; - - // draw data - paint.setStrokeWidth(style.thickness); - paint.setColor(style.color); - - - Path bgPath = null; - if (drawBackground) { - bgPath = new Path(); - } - - lastEndY = 0; - lastEndX = 0; - float firstX = 0; - for (int i = 0; i < values.length; i++) { - double valY = values[i].getY() - minY; - double ratY = valY / diffY; - double y = graphheight * ratY; - - double valX = values[i].getX() - minX; - double ratX = valX / diffX; - double x = graphwidth * ratX; - - if (i > 0) { - float startX = (float) lastEndX + (horstart + 1); - float startY = (float) (border - lastEndY) + graphheight; - float endX = (float) x + (horstart + 1); - float endY = (float) (border - y) + graphheight; - - // draw data point - if (drawDataPoints) { - //fix: last value was not drawn. Draw here now the end values - canvas.drawCircle(endX, endY, dataPointsRadius, paint); - } - - canvas.drawLine(startX, startY, endX, endY, paint); - if (bgPath != null) { - if (i==1) { - firstX = startX; - bgPath.moveTo(startX, startY); - } - bgPath.lineTo(endX, endY); - } - } else if (drawDataPoints) { - //fix: last value not drawn as datapoint. Draw first point here, and then on every step the end values (above) - float first_X = (float) x + (horstart + 1); - float first_Y = (float) (border - y) + graphheight; - canvas.drawCircle(first_X, first_Y, dataPointsRadius, paint); - } - lastEndY = y; - lastEndX = x; - } - - if (bgPath != null) { - // end / close path - bgPath.lineTo((float) lastEndX, graphheight + border); - bgPath.lineTo(firstX, graphheight + border); - bgPath.close(); - canvas.drawPath(bgPath, paintBackground); - } - } - - public int getBackgroundColor() { - return paintBackground.getColor(); - } - - public float getDataPointsRadius() { - return dataPointsRadius; - } - - public boolean getDrawBackground() { - return drawBackground; - } - - public boolean getDrawDataPoints() { - return drawDataPoints; - } - - /** - * sets the background color for the series. - * This is not the background color of the whole graph. - * @see #setDrawBackground(boolean) - */ - @Override - public void setBackgroundColor(int color) { - paintBackground.setColor(color); - } - - /** - * sets the radius of the circles at the data points. - * @see #setDrawDataPoints(boolean) - * @param dataPointsRadius - */ - public void setDataPointsRadius(float dataPointsRadius) { - this.dataPointsRadius = dataPointsRadius; - } - - /** - * @param drawBackground true for a light blue background under the graph line - * @see #setBackgroundColor(int) - */ - public void setDrawBackground(boolean drawBackground) { - this.drawBackground = drawBackground; - } - - /** - * You can set the flag to let the GraphView draw circles at the data points - * @see #setDataPointsRadius(float) - * @param drawDataPoints - */ - public void setDrawDataPoints(boolean drawDataPoints) { - this.drawDataPoints = drawDataPoints; - } - -} diff --git a/src/com/jjoe64/graphview/ValueDependentColor.java b/src/com/jjoe64/graphview/ValueDependentColor.java deleted file mode 100644 index 7b406ceed..000000000 --- a/src/com/jjoe64/graphview/ValueDependentColor.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * This file is part of GraphView. - * - * GraphView is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * GraphView is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GraphView. If not, see . - * - * Copyright Jonas Gehring - */ - -package com.jjoe64.graphview; - -/** - * you can change the color depending on the value. - * takes only effect in BarGraphView - */ -public interface ValueDependentColor { - public int get(GraphViewDataInterface data); -} diff --git a/src/com/jjoe64/graphview/compatible/RealScaleGestureDetector.java b/src/com/jjoe64/graphview/compatible/RealScaleGestureDetector.java deleted file mode 100644 index a9143b034..000000000 --- a/src/com/jjoe64/graphview/compatible/RealScaleGestureDetector.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * This file is part of GraphView. - * - * GraphView is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * GraphView is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GraphView. If not, see . - * - * Copyright Jonas Gehring - */ - -package com.jjoe64.graphview.compatible; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.view.ScaleGestureDetector; - -@SuppressLint("NewApi") -public class RealScaleGestureDetector extends ScaleGestureDetector { - public RealScaleGestureDetector(Context context, final com.jjoe64.graphview.compatible.ScaleGestureDetector fakeScaleGestureDetector, final com.jjoe64.graphview.compatible.ScaleGestureDetector.SimpleOnScaleGestureListener fakeListener) { - super(context, new android.view.ScaleGestureDetector.SimpleOnScaleGestureListener() { - @Override - public boolean onScale(ScaleGestureDetector detector) { - return fakeListener.onScale(fakeScaleGestureDetector); - } - }); - } -} diff --git a/src/com/jjoe64/graphview/compatible/ScaleGestureDetector.java b/src/com/jjoe64/graphview/compatible/ScaleGestureDetector.java deleted file mode 100644 index 3a29d4bd7..000000000 --- a/src/com/jjoe64/graphview/compatible/ScaleGestureDetector.java +++ /dev/null @@ -1,97 +0,0 @@ -/** - * This file is part of GraphView. - * - * GraphView is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * GraphView is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GraphView. If not, see . - * - * Copyright Jonas Gehring - */ - -package com.jjoe64.graphview.compatible; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; - -import android.content.Context; -import android.util.Log; -import android.view.MotionEvent; - -public class ScaleGestureDetector { - public interface SimpleOnScaleGestureListener { - boolean onScale(ScaleGestureDetector detector); - } - - private Object realScaleGestureDetector; - private Method method_getScaleFactor; - private Method method_isInProgress; - private Method method_onTouchEvent; - - /** - * @param context - * @param simpleOnScaleGestureListener - */ - public ScaleGestureDetector(Context context, SimpleOnScaleGestureListener simpleOnScaleGestureListener) { - try { - // check if class is available - Class.forName("android.view.ScaleGestureDetector"); - - // load class and methods - Class classRealScaleGestureDetector = Class.forName("com.jjoe64.graphview.compatible.RealScaleGestureDetector"); - method_getScaleFactor = classRealScaleGestureDetector.getMethod("getScaleFactor"); - method_isInProgress = classRealScaleGestureDetector.getMethod("isInProgress"); - method_onTouchEvent = classRealScaleGestureDetector.getMethod("onTouchEvent", MotionEvent.class); - - // create real ScaleGestureDetector - Constructor constructor = classRealScaleGestureDetector.getConstructor(Context.class, getClass(), SimpleOnScaleGestureListener.class); - realScaleGestureDetector = constructor.newInstance(context, this, simpleOnScaleGestureListener); - } catch (Exception e) { - // not available - Log.w("com.jjoe64.graphview", "*** WARNING *** No scaling available for graphs. Exception:"); - e.printStackTrace(); - } - } - - public double getScaleFactor() { - if (method_getScaleFactor != null) { - try { - return (Float) method_getScaleFactor.invoke(realScaleGestureDetector); - } catch (Exception e) { - e.printStackTrace(); - return 1.0; - } - } - return 1.0; - } - - public boolean isInProgress() { - if (method_getScaleFactor != null) { - try { - return (Boolean) method_isInProgress.invoke(realScaleGestureDetector); - } catch (Exception e) { - e.printStackTrace(); - return false; - } - } - return false; - } - - public void onTouchEvent(MotionEvent event) { - if (method_onTouchEvent != null) { - try { - method_onTouchEvent.invoke(realScaleGestureDetector, event); - } catch (Exception e) { - e.printStackTrace(); - } - } - } -} diff --git a/AndroidManifest.xml b/src/main/AndroidManifest.xml similarity index 79% rename from AndroidManifest.xml rename to src/main/AndroidManifest.xml index 7f08a835d..120f744d6 100644 --- a/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -3,5 +3,6 @@ package="com.jjoe64.graphview" android:versionCode="1" android:versionName="1.0"> - + + diff --git a/src/main/java/com/jjoe64/graphview/CursorMode.java b/src/main/java/com/jjoe64/graphview/CursorMode.java new file mode 100644 index 000000000..e42369148 --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/CursorMode.java @@ -0,0 +1,254 @@ +package com.jjoe64.graphview; + +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; +import android.util.Log; +import android.util.TypedValue; +import android.view.MotionEvent; + +import com.jjoe64.graphview.series.BaseSeries; +import com.jjoe64.graphview.series.DataPointInterface; +import com.jjoe64.graphview.series.Series; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Created by jonas on 22/02/2017. + */ + +public class CursorMode { + private final static class Styles { + public float textSize; + public int spacing; + public int padding; + public int width; + public int backgroundColor; + public int margin; + public int textColor; + } + + protected final Paint mPaintLine; + protected final GraphView mGraphView; + protected float mPosX; + protected float mPosY; + protected boolean mCursorVisible; + protected final Map mCurrentSelection; + protected final Paint mRectPaint; + protected final Paint mTextPaint; + protected double mCurrentSelectionX; + protected Styles mStyles; + protected int cachedLegendWidth; + + public CursorMode(GraphView graphView) { + mStyles = new Styles(); + mGraphView = graphView; + mPaintLine = new Paint(); + mPaintLine.setColor(Color.argb(128, 180, 180, 180)); + mPaintLine.setStrokeWidth(10f); + mCurrentSelection = new HashMap<>(); + mRectPaint = new Paint(); + mTextPaint = new Paint(); + resetStyles(); + } + + /** + * resets the styles to the defaults + * and clears the legend width cache + */ + public void resetStyles() { + mStyles.textSize = mGraphView.getGridLabelRenderer().getTextSize(); + mStyles.spacing = (int) (mStyles.textSize / 5); + mStyles.padding = (int) (mStyles.textSize / 2); + mStyles.width = 0; + mStyles.backgroundColor = Color.argb(180, 100, 100, 100); + mStyles.margin = (int) (mStyles.textSize); + + // get matching styles from theme + TypedValue typedValue = new TypedValue(); + mGraphView.getContext().getTheme().resolveAttribute(android.R.attr.textAppearanceSmall, typedValue, true); + + int color1; + + try { + TypedArray array = mGraphView.getContext().obtainStyledAttributes(typedValue.data, new int[]{ + android.R.attr.textColorPrimary}); + color1 = array.getColor(0, Color.BLACK); + array.recycle(); + } catch (Exception e) { + color1 = Color.BLACK; + } + + mStyles.textColor = color1; + + cachedLegendWidth = 0; + } + + + public void onDown(MotionEvent e) { + mPosX = Math.max(e.getX(), mGraphView.getGraphContentLeft()); + mPosX = Math.min(mPosX, mGraphView.getGraphContentLeft() + mGraphView.getGraphContentWidth()); + mPosY = e.getY(); + mCursorVisible = true; + findCurrentDataPoint(); + mGraphView.invalidate(); + } + + public void onMove(MotionEvent e) { + if (mCursorVisible) { + mPosX = Math.max(e.getX(), mGraphView.getGraphContentLeft()); + mPosX = Math.min(mPosX, mGraphView.getGraphContentLeft() + mGraphView.getGraphContentWidth()); + mPosY = e.getY(); + findCurrentDataPoint(); + mGraphView.invalidate(); + } + } + + public void draw(Canvas canvas) { + if (mCursorVisible) { + canvas.drawLine(mPosX, 0, mPosX, canvas.getHeight(), mPaintLine); + } + + // selection + for (Map.Entry entry : mCurrentSelection.entrySet()) { + entry.getKey().drawSelection(mGraphView, canvas, false, entry.getValue()); + } + + if (!mCurrentSelection.isEmpty()) { + drawLegend(canvas); + } + } + + protected String getTextForSeries(Series s, DataPointInterface value) { + StringBuffer txt = new StringBuffer(); + if (s.getTitle() != null) { + txt.append(s.getTitle()); + txt.append(": "); + } + txt.append(mGraphView.getGridLabelRenderer().getLabelFormatter().formatLabel(value.getY(), false)); + return txt.toString(); + } + + protected void drawLegend(Canvas canvas) { + mTextPaint.setTextSize(mStyles.textSize); + mTextPaint.setColor(mStyles.textColor); + + int shapeSize = (int) (mStyles.textSize*0.8d); + + // width + int legendWidth = mStyles.width; + if (legendWidth == 0) { + // auto + legendWidth = cachedLegendWidth; + + if (legendWidth == 0) { + Rect textBounds = new Rect(); + for (Map.Entry entry : mCurrentSelection.entrySet()) { + String txt = getTextForSeries(entry.getKey(), entry.getValue()); + mTextPaint.getTextBounds(txt, 0, txt.length(), textBounds); + legendWidth = Math.max(legendWidth, textBounds.width()); + } + if (legendWidth == 0) legendWidth = 1; + + // add shape size + legendWidth += shapeSize+mStyles.padding*2 + mStyles.spacing; + cachedLegendWidth = legendWidth; + } + } + + float legendPosX = mPosX - mStyles.margin - legendWidth; + if (legendPosX < 0) { + legendPosX = 0; + } + + // rect + float legendHeight = (mStyles.textSize+mStyles.spacing) * (mCurrentSelection.size() + 1) -mStyles.spacing; + + float legendPosY = mPosY - legendHeight - 4.5f * mStyles.textSize; + if (legendPosY < 0) { + legendPosY = 0; + } + + float lLeft; + float lTop; + lLeft = legendPosX; + lTop = legendPosY; + + float lRight = lLeft+legendWidth; + float lBottom = lTop+legendHeight+2*mStyles.padding; + mRectPaint.setColor(mStyles.backgroundColor); + canvas.drawRoundRect(new RectF(lLeft, lTop, lRight, lBottom), 8, 8, mRectPaint); + + mTextPaint.setFakeBoldText(true); + canvas.drawText(mGraphView.getGridLabelRenderer().getLabelFormatter().formatLabel(mCurrentSelectionX, true), lLeft+mStyles.padding, lTop+mStyles.padding/2+mStyles.textSize, mTextPaint); + + mTextPaint.setFakeBoldText(false); + + int i=1; + for (Map.Entry entry : mCurrentSelection.entrySet()) { + mRectPaint.setColor(entry.getKey().getColor()); + canvas.drawRect(new RectF(lLeft+mStyles.padding, lTop+mStyles.padding+(i*(mStyles.textSize+mStyles.spacing)), lLeft+mStyles.padding+shapeSize, lTop+mStyles.padding+(i*(mStyles.textSize+mStyles.spacing))+shapeSize), mRectPaint); + canvas.drawText(getTextForSeries(entry.getKey(), entry.getValue()), lLeft+mStyles.padding+shapeSize+mStyles.spacing, lTop+mStyles.padding/2+mStyles.textSize+(i*(mStyles.textSize+mStyles.spacing)), mTextPaint); + i++; + } + } + + public boolean onUp(MotionEvent event) { + mCursorVisible = false; + findCurrentDataPoint(); + mGraphView.invalidate(); + return true; + } + + private void findCurrentDataPoint() { + double selX = 0; + mCurrentSelection.clear(); + for (Series series : mGraphView.getSeries()) { + if (series instanceof BaseSeries) { + DataPointInterface p = ((BaseSeries) series).findDataPointAtX(mPosX); + if (p != null) { + selX = p.getX(); + mCurrentSelection.put((BaseSeries) series, p); + } + } + } + + if (!mCurrentSelection.isEmpty()) { + mCurrentSelectionX = selX; + } + } + + public void setTextSize(float t) { + mStyles.textSize = t; + } + + public void setTextColor(int color) { + mStyles.textColor = color; + } + + public void setBackgroundColor(int color) { + mStyles.backgroundColor = color; + } + + public void setSpacing(int s) { + mStyles.spacing = s; + } + + public void setPadding(int s) { + mStyles.padding = s; + } + + public void setMargin(int s) { + mStyles.margin = s; + } + + public void setWidth(int s) { + mStyles.width = s; + } +} diff --git a/src/main/java/com/jjoe64/graphview/DefaultLabelFormatter.java b/src/main/java/com/jjoe64/graphview/DefaultLabelFormatter.java new file mode 100644 index 000000000..c22c44626 --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/DefaultLabelFormatter.java @@ -0,0 +1,102 @@ +/** + * GraphView + * Copyright 2016 Jonas Gehring + * + * 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.jjoe64.graphview; + +import java.text.NumberFormat; + +/** + * The label formatter that will be used + * by default. + * It will use the NumberFormat from Android + * and sets the maximal fraction digits + * depending on the range between min and max + * value of the current viewport. + * + * It is recommended to use this label formatter + * as base class to implement a custom formatter. + * + * @author jjoe64 + */ +public class DefaultLabelFormatter implements LabelFormatter { + /** + * number formatter for x and y values + */ + protected NumberFormat[] mNumberFormatter = new NumberFormat[2]; + + /** + * reference to the viewport of the + * graph. + * Will be used to calculate the current + * range of values. + */ + protected Viewport mViewport; + + /** + * uses the default number format for the labels + */ + public DefaultLabelFormatter() { + } + + /** + * use custom number format + * + * @param xFormat the number format for the x labels + * @param yFormat the number format for the y labels + */ + public DefaultLabelFormatter(NumberFormat xFormat, NumberFormat yFormat) { + mNumberFormatter[0] = yFormat; + mNumberFormatter[1] = xFormat; + } + + /** + * @param viewport the viewport of the graph + */ + @Override + public void setViewport(Viewport viewport) { + mViewport = viewport; + } + + /** + * Formats the raw value to a nice + * looking label, depending on the + * current range of the viewport. + * + * @param value raw value + * @param isValueX true if it's a x value, otherwise false + * @return the formatted value as string + */ + public String formatLabel(double value, boolean isValueX) { + int i = isValueX ? 1 : 0; + if (mNumberFormatter[i] == null) { + mNumberFormatter[i] = NumberFormat.getNumberInstance(); + double highestvalue = isValueX ? mViewport.getMaxX(false) : mViewport.getMaxY(false); + double lowestvalue = isValueX ? mViewport.getMinX(false) : mViewport.getMinY(false); + if (highestvalue - lowestvalue < 0.1) { + mNumberFormatter[i].setMaximumFractionDigits(6); + } else if (highestvalue - lowestvalue < 1) { + mNumberFormatter[i].setMaximumFractionDigits(4); + } else if (highestvalue - lowestvalue < 20) { + mNumberFormatter[i].setMaximumFractionDigits(3); + } else if (highestvalue - lowestvalue < 100) { + mNumberFormatter[i].setMaximumFractionDigits(1); + } else { + mNumberFormatter[i].setMaximumFractionDigits(0); + } + } + return mNumberFormatter[i].format(value); + } +} diff --git a/src/main/java/com/jjoe64/graphview/GraphView.java b/src/main/java/com/jjoe64/graphview/GraphView.java new file mode 100644 index 000000000..a01da0fbf --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/GraphView.java @@ -0,0 +1,658 @@ +/** + * GraphView + * Copyright 2016 Jonas Gehring + * + * 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.jjoe64.graphview; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PointF; +import android.net.Uri; +import android.provider.MediaStore; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; + +import com.jjoe64.graphview.series.BaseSeries; +import com.jjoe64.graphview.series.Series; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * @author jjoe64 + */ +public class GraphView extends View { + /** + * Class to wrap style options that are general + * to graphs. + * + * @author jjoe64 + */ + private static final class Styles { + /** + * The font size of the title that can be displayed + * above the graph. + * + * @see GraphView#setTitle(String) + */ + float titleTextSize; + + /** + * The font color of the title that can be displayed + * above the graph. + * + * @see GraphView#setTitle(String) + */ + int titleColor; + } + + /** + * Helper class to detect tap events on the + * graph. + * + * @author jjoe64 + */ + private class TapDetector { + /** + * save the time of the last down event + */ + private long lastDown; + + /** + * point of the tap down event + */ + private PointF lastPoint; + + /** + * to be called to process the events + * + * @param event + * @return true if there was a tap event. otherwise returns false. + */ + public boolean onTouchEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + lastDown = System.currentTimeMillis(); + lastPoint = new PointF(event.getX(), event.getY()); + } else if (lastDown > 0 && event.getAction() == MotionEvent.ACTION_MOVE) { + if (Math.abs(event.getX() - lastPoint.x) > 60 + || Math.abs(event.getY() - lastPoint.y) > 60) { + lastDown = 0; + } + } else if (event.getAction() == MotionEvent.ACTION_UP) { + if (System.currentTimeMillis() - lastDown < 400) { + return true; + } + } + return false; + } + } + + /** + * our series (this does not contain the series + * that can be displayed on the right side. The + * right side series is a special feature of + * the {@link SecondScale} feature. + */ + private List mSeries; + + /** + * the renderer for the grid and labels + */ + private GridLabelRenderer mGridLabelRenderer; + + /** + * viewport that holds the current bounds of + * view. + */ + private Viewport mViewport; + + /** + * title of the graph that will be shown above + */ + private String mTitle; + + /** + * wraps the general styles + */ + private Styles mStyles; + + /** + * feature to have a second scale e.g. on the + * right side + */ + protected SecondScale mSecondScale; + + /** + * tap detector + */ + private TapDetector mTapDetector; + + /** + * renderer for the legend + */ + private LegendRenderer mLegendRenderer; + + /** + * paint for the graph title + */ + private Paint mPaintTitle; + + private boolean mIsCursorMode; + + /** + * paint for the preview (in the SDK) + */ + private Paint mPreviewPaint; + + private CursorMode mCursorMode; + + /** + * Initialize the GraphView view + * @param context + */ + public GraphView(Context context) { + super(context); + init(); + } + + /** + * Initialize the GraphView view. + * + * @param context + * @param attrs + */ + public GraphView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + /** + * Initialize the GraphView view + * + * @param context + * @param attrs + * @param defStyle + */ + public GraphView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + /** + * initialize the internal objects. + * This method has to be called directly + * in the constructors. + */ + protected void init() { + mPreviewPaint = new Paint(); + mPreviewPaint.setTextAlign(Paint.Align.CENTER); + mPreviewPaint.setColor(Color.BLACK); + mPreviewPaint.setTextSize(50); + + mStyles = new Styles(); + mViewport = new Viewport(this); + mGridLabelRenderer = new GridLabelRenderer(this); + mLegendRenderer = new LegendRenderer(this); + + mSeries = new ArrayList(); + mPaintTitle = new Paint(); + + mTapDetector = new TapDetector(); + + loadStyles(); + } + + /** + * loads the font + */ + protected void loadStyles() { + mStyles.titleColor = mGridLabelRenderer.getHorizontalLabelsColor(); + mStyles.titleTextSize = mGridLabelRenderer.getTextSize(); + } + + /** + * @return the renderer for the grid and labels + */ + public GridLabelRenderer getGridLabelRenderer() { + return mGridLabelRenderer; + } + + /** + * Add a new series to the graph. This will + * automatically redraw the graph. + * @param s the series to be added + */ + public void addSeries(Series s) { + s.onGraphViewAttached(this); + mSeries.add(s); + onDataChanged(false, false); + } + + /** + * important: do not do modifications on the list + * object that will be returned. + * Use {@link #removeSeries(com.jjoe64.graphview.series.Series)} and {@link #addSeries(com.jjoe64.graphview.series.Series)} + * + * @return all series + */ + public List getSeries() { + // TODO immutable array + return mSeries; + } + + /** + * call this to let the graph redraw and + * recalculate the viewport. + * This will be called when a new series + * was added or removed and when data + * was appended via {@link com.jjoe64.graphview.series.BaseSeries#appendData(com.jjoe64.graphview.series.DataPointInterface, boolean, int)} + * or {@link com.jjoe64.graphview.series.BaseSeries#resetData(com.jjoe64.graphview.series.DataPointInterface[])}. + * + * @param keepLabelsSize true if you don't want + * to recalculate the size of + * the labels. It is recommended + * to use "true" because this will + * improve performance and prevent + * a flickering. + * @param keepViewport true if you don't want that + * the viewport will be recalculated. + * It is recommended to use "true" for + * performance. + */ + public void onDataChanged(boolean keepLabelsSize, boolean keepViewport) { + // adjustSteps grid system + mViewport.calcCompleteRange(); + if (mSecondScale != null) { + mSecondScale.calcCompleteRange(); + } + mGridLabelRenderer.invalidate(keepLabelsSize, keepViewport); + postInvalidate(); + } + + /** + * draw all the stuff on canvas + * + * @param canvas + */ + protected void drawGraphElements(Canvas canvas) { + // must be in hardware accelerated mode + if (android.os.Build.VERSION.SDK_INT >= 11 && !canvas.isHardwareAccelerated()) { + // just warn about it, because it is ok when making a snapshot + Log.w("GraphView", "GraphView should be used in hardware accelerated mode." + + "You can use android:hardwareAccelerated=\"true\" on your activity. Read this for more info:" + + "https://developer.android.com/guide/topics/graphics/hardware-accel.html"); + } + + drawTitle(canvas); + mViewport.drawFirst(canvas); + mGridLabelRenderer.draw(canvas); + for (Series s : mSeries) { + s.draw(this, canvas, false); + } + if (mSecondScale != null) { + for (Series s : mSecondScale.getSeries()) { + s.draw(this, canvas, true); + } + } + + if (mCursorMode != null) { + mCursorMode.draw(canvas); + } + + mViewport.draw(canvas); + mLegendRenderer.draw(canvas); + } + + /** + * will be called from Android system. + * + * @param canvas Canvas + */ + @Override + protected void onDraw(Canvas canvas) { + if (isInEditMode()) { + canvas.drawColor(Color.rgb(200, 200, 200)); + canvas.drawText("GraphView: No Preview available", canvas.getWidth()/2, canvas.getHeight()/2, mPreviewPaint); + } else { + drawGraphElements(canvas); + } + } + + /** + * Draws the Graphs title that will be + * shown above the viewport. + * Will be called by GraphView. + * + * @param canvas Canvas + */ + protected void drawTitle(Canvas canvas) { + if (mTitle != null && mTitle.length()>0) { + mPaintTitle.setColor(mStyles.titleColor); + mPaintTitle.setTextSize(mStyles.titleTextSize); + mPaintTitle.setTextAlign(Paint.Align.CENTER); + float x = canvas.getWidth()/2; + float y = mPaintTitle.getTextSize(); + canvas.drawText(mTitle, x, y, mPaintTitle); + } + } + + /** + * Calculates the height of the title. + * + * @return the actual size of the title. + * if there is no title, 0 will be + * returned. + */ + protected int getTitleHeight() { + if (mTitle != null && mTitle.length()>0) { + return (int) mPaintTitle.getTextSize(); + } else { + return 0; + } + } + + /** + * @return the viewport of the Graph. + * @see com.jjoe64.graphview.Viewport + */ + public Viewport getViewport() { + return mViewport; + } + + /** + * Called by Android system if the size + * of the view was changed. Will recalculate + * the viewport and labels. + * + * @param w + * @param h + * @param oldw + * @param oldh + */ + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + onDataChanged(false, false); + } + + /** + * @return the space on the left side of the + * view from the left border to the + * beginning of the graph viewport. + */ + public int getGraphContentLeft() { + int border = getGridLabelRenderer().getStyles().padding; + return border + getGridLabelRenderer().getLabelVerticalWidth() + getGridLabelRenderer().getVerticalAxisTitleWidth(); + } + + /** + * @return the space on the top of the + * view from the top border to the + * beginning of the graph viewport. + */ + public int getGraphContentTop() { + int border = getGridLabelRenderer().getStyles().padding + getTitleHeight(); + return border; + } + + /** + * @return the height of the graph viewport. + */ + public int getGraphContentHeight() { + int border = getGridLabelRenderer().getStyles().padding; + int graphheight = getHeight() - (2 * border) - getGridLabelRenderer().getLabelHorizontalHeight() - getTitleHeight(); + graphheight -= getGridLabelRenderer().getHorizontalAxisTitleHeight(); + return graphheight; + } + + /** + * @return the width of the graph viewport. + */ + public int getGraphContentWidth() { + int border = getGridLabelRenderer().getStyles().padding; + int graphwidth = getWidth() - (2 * border) - getGridLabelRenderer().getLabelVerticalWidth(); + if (mSecondScale != null) { + graphwidth -= getGridLabelRenderer().getLabelVerticalSecondScaleWidth(); + graphwidth -= mSecondScale.getVerticalAxisTitleTextSize(); + } + return graphwidth; + } + + /** + * will be called from Android system. + * + * @param event + * @return + */ + @Override + public boolean onTouchEvent(MotionEvent event) { + boolean b = mViewport.onTouchEvent(event); + boolean a = super.onTouchEvent(event); + + // is it a click? + if (mTapDetector.onTouchEvent(event)) { + for (Series s : mSeries) { + s.onTap(event.getX(), event.getY()); + } + if (mSecondScale != null) { + for (Series s : mSecondScale.getSeries()) { + s.onTap(event.getX(), event.getY()); + } + } + } + + return b || a; + } + + /** + * + */ + @Override + public void computeScroll() { + super.computeScroll(); + mViewport.computeScroll(); + } + + /** + * @return the legend renderer. + * @see com.jjoe64.graphview.LegendRenderer + */ + public LegendRenderer getLegendRenderer() { + return mLegendRenderer; + } + + /** + * use a specific legend renderer + * + * @param mLegendRenderer the new legend renderer + */ + public void setLegendRenderer(LegendRenderer mLegendRenderer) { + this.mLegendRenderer = mLegendRenderer; + } + + /** + * @return the title that will be shown + * above the graph. + */ + public String getTitle() { + return mTitle; + } + + /** + * Set the title of the graph that will + * be shown above the graph's viewport. + * + * @param mTitle the title + * @see #setTitleColor(int) to set the font color + * @see #setTitleTextSize(float) to set the font size + */ + public void setTitle(String mTitle) { + this.mTitle = mTitle; + } + + /** + * @return the title font size + */ + public float getTitleTextSize() { + return mStyles.titleTextSize; + } + + /** + * Set the title's font size + * + * @param titleTextSize font size + * @see #setTitle(String) + */ + public void setTitleTextSize(float titleTextSize) { + mStyles.titleTextSize = titleTextSize; + } + + /** + * @return font color of the title + */ + public int getTitleColor() { + return mStyles.titleColor; + } + + /** + * Set the title's font color + * + * @param titleColor font color of the title + * @see #setTitle(String) + */ + public void setTitleColor(int titleColor) { + mStyles.titleColor = titleColor; + } + + /** + * creates the second scale logic and returns it + * + * @return second scale object + */ + public SecondScale getSecondScale() { + if (mSecondScale == null) { + // this creates the second scale + mSecondScale = new SecondScale(this); + mSecondScale.setVerticalAxisTitleTextSize(mGridLabelRenderer.mStyles.textSize); + } + return mSecondScale; + } + + /** + * clears the second scale + */ + public void clearSecondScale() { + if (mSecondScale != null) { + mSecondScale.removeAllSeries(); + mSecondScale = null; + } + } + + /** + * Removes all series of the graph. + */ + public void removeAllSeries() { + mSeries.clear(); + onDataChanged(false, false); + } + + /** + * Remove a specific series of the graph. + * This will also re-draw the graph, but + * without recalculating the viewport and + * label sizes. + * If you want this, you have to call {@link #onDataChanged(boolean, boolean)} + * manually. + * + * @param series + */ + public void removeSeries(Series series) { + mSeries.remove(series); + onDataChanged(false, false); + } + + /** + * takes a snapshot and return it as bitmap + * + * @return snapshot of graph + */ + public Bitmap takeSnapshot() { + Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + draw(canvas); + return bitmap; + } + + /** + * takes a snapshot, stores it and open the share dialog. + * Notice that you need the permission android.permission.WRITE_EXTERNAL_STORAGE + * + * @param context + * @param imageName + * @param title + */ + public void takeSnapshotAndShare(Context context, String imageName, String title) { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + Bitmap inImage = takeSnapshot(); + inImage.compress(Bitmap.CompressFormat.JPEG, 100, bytes); + + String path = MediaStore.Images.Media.insertImage(context.getContentResolver(), inImage, imageName, null); + if (path == null) { + // most likely a security problem + throw new SecurityException("Could not get path from MediaStore. Please check permissions."); + } + + Intent i = new Intent(Intent.ACTION_SEND); + i.setType("image/*"); + i.putExtra(Intent.EXTRA_STREAM, Uri.parse(path)); + try { + context.startActivity(Intent.createChooser(i, title)); + } catch (android.content.ActivityNotFoundException ex) { + ex.printStackTrace(); + } + } + + public void setCursorMode(boolean b) { + mIsCursorMode = b; + if (mIsCursorMode) { + if (mCursorMode == null) { + mCursorMode = new CursorMode(this); + } + } else { + mCursorMode = null; + invalidate(); + } + for (Series series : mSeries) { + if (series instanceof BaseSeries) { + ((BaseSeries) series).clearCursorModeCache(); + } + } + } + + public CursorMode getCursorMode() { + return mCursorMode; + } + + public boolean isCursorMode() { + return mIsCursorMode; + } +} diff --git a/src/main/java/com/jjoe64/graphview/GridLabelRenderer.java b/src/main/java/com/jjoe64/graphview/GridLabelRenderer.java new file mode 100644 index 000000000..37210b4e7 --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/GridLabelRenderer.java @@ -0,0 +1,1813 @@ +/** + * GraphView + * Copyright 2016 Jonas Gehring + * + * 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.jjoe64.graphview; + +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.util.TypedValue; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * The default renderer for the grid + * and the labels. + * + * @author jjoe64 + */ +public class GridLabelRenderer { + + /** + * Hoziontal label alignment + */ + public enum VerticalLabelsVAlign { + /** + * Above vertical line + */ + ABOVE, + /** + * Mid vertical line + */ + MID, + /** + * Below vertical line + */ + BELOW + } + + + /** + * wrapper for the styles regarding + * to the grid and the labels + */ + public final class Styles { + /** + * the general text size of the axis titles. + * can be overwritten with #verticalAxisTitleTextSize + * and #horizontalAxisTitleTextSize + */ + public float textSize; + + /** + * the alignment of the vertical labels + */ + public Paint.Align verticalLabelsAlign; + + /** + * the alignment of the labels on the right side + */ + public Paint.Align verticalLabelsSecondScaleAlign; + + /** + * the color of the vertical labels + */ + public int verticalLabelsColor; + + /** + * the color of the labels on the right side + */ + public int verticalLabelsSecondScaleColor; + + /** + * the color of the horizontal labels + */ + public int horizontalLabelsColor; + + /** + * the color of the grid lines + */ + public int gridColor; + + /** + * flag whether the zero-lines (vertical+ + * horizontal) shall be highlighted + */ + public boolean highlightZeroLines; + + /** + * the padding around the graph and labels + */ + public int padding; + + /** + * font size of the vertical axis title + */ + public float verticalAxisTitleTextSize; + + /** + * font color of the vertical axis title + */ + public int verticalAxisTitleColor; + + /** + * font size of the horizontal axis title + */ + public float horizontalAxisTitleTextSize; + + /** + * font color of the horizontal axis title + */ + public int horizontalAxisTitleColor; + + /** + * angle of the horizontal axis label in + * degrees between 0 and 180 + */ + public float horizontalLabelsAngle; + + /** + * flag whether the horizontal labels are + * visible + */ + boolean horizontalLabelsVisible; + + /** + * flag whether the vertical labels are + * visible + */ + boolean verticalLabelsVisible; + + /** + * defines which lines will be drawn in the background + */ + GridStyle gridStyle; + + /** + * the space between the labels text and the graph content + */ + int labelsSpace; + + /** + * vertical labels vertical align (above, below, mid of the grid line) + */ + VerticalLabelsVAlign verticalLabelsVAlign = VerticalLabelsVAlign.MID; + } + + /** + * Definition which lines will be drawn in the background + */ + public enum GridStyle { + /** + * show vertical and horizonal lines + * this is the default + */ + BOTH, + + /** + * show only vertical lines + */ + VERTICAL, + + /** + * show only horizontal lines + */ + HORIZONTAL, + + /** + * dont draw any lines + */ + NONE; + + public boolean drawVertical() { return this == BOTH || this == VERTICAL && this != NONE; } + public boolean drawHorizontal() { return this == BOTH || this == HORIZONTAL && this != NONE; } + } + + /** + * wraps the styles regarding the + * grid and labels + */ + protected Styles mStyles; + + /** + * reference to graphview + */ + private final GraphView mGraphView; + + /** + * cache of the vertical steps + * (horizontal lines and vertical labels) + * Key = Pixel (y) + * Value = y-value + */ + private Map mStepsVertical; + + /** + * cache of the vertical steps for the + * second scale, which is on the right side + * (horizontal lines and vertical labels) + * Key = Pixel (y) + * Value = y-value + */ + private Map mStepsVerticalSecondScale; + + /** + * cache of the horizontal steps + * (vertical lines and horizontal labels) + * Value = x-value + */ + private Map mStepsHorizontal; + + /** + * the paint to draw the grid lines + */ + private Paint mPaintLine; + + /** + * the paint to draw the labels + */ + private Paint mPaintLabel; + + /** + * the paint to draw axis titles + */ + private Paint mPaintAxisTitle; + + /** + * flag whether is bounds are automatically + * adjusted for nice human-readable numbers + */ + protected boolean mIsAdjusted; + + /** + * the width of the vertical labels + */ + private Integer mLabelVerticalWidth; + + /** + * indicates if the width was set manually + */ + private boolean mLabelVerticalWidthFixed; + + /** + * the height of the vertical labels + */ + private Integer mLabelVerticalHeight; + + /** + * indicates if the height was set manually + */ + private boolean mLabelHorizontalHeightFixed; + + /** + * the width of the vertical labels + * of the second scale + */ + private Integer mLabelVerticalSecondScaleWidth; + + /** + * the height of the vertical labels + * of the second scale + */ + private Integer mLabelVerticalSecondScaleHeight; + + /** + * the width of the horizontal labels + */ + private Integer mLabelHorizontalWidth; + + /** + * the height of the horizontal labels + */ + private Integer mLabelHorizontalHeight; + + /** + * the label formatter, that converts + * the raw numbers to strings + */ + private LabelFormatter mLabelFormatter; + + /** + * the title of the horizontal axis + */ + private String mHorizontalAxisTitle; + + /** + * the title of the vertical axis + */ + private String mVerticalAxisTitle; + + /** + * count of the vertical labels, that + * will be shown at one time. + */ + private int mNumVerticalLabels; + + /** + * count of the horizontal labels, that + * will be shown at one time. + */ + private int mNumHorizontalLabels; + + /** + * sets the space for the vertical labels on the right side + * + * @param newWidth set fixed width. set null to calculate it automatically + */ + public void setSecondScaleLabelVerticalWidth(Integer newWidth) { + mLabelVerticalSecondScaleWidth = newWidth; + } + + /** + * activate or deactivate human rounding of the + * horizontal axis. GraphView tries to fit the labels + * to display numbers that can be divided by 1, 2, or 5. + */ + private boolean mHumanRoundingY; + + /** + * activate or deactivate human rounding of the + * horizontal axis. GraphView tries to fit the labels + * to display numbers that can be divided by 1, 2, or 5. + * + * By default this is enabled. It makes sense to deactivate it + * when using Dates on the x axis. + */ + private boolean mHumanRoundingX; + + /** + * create the default grid label renderer. + * + * @param graphView the corresponding graphview object + */ + public GridLabelRenderer(GraphView graphView) { + mGraphView = graphView; + setLabelFormatter(new DefaultLabelFormatter()); + mStyles = new Styles(); + resetStyles(); + mNumVerticalLabels = 5; + mNumHorizontalLabels = 5; + mHumanRoundingX = true; + mHumanRoundingY = true; + } + + /** + * resets the styles. This loads the style + * from reading the values of the current + * theme. + */ + public void resetStyles() { + // get matching styles from theme + TypedValue typedValue = new TypedValue(); + mGraphView.getContext().getTheme().resolveAttribute(android.R.attr.textAppearanceSmall, typedValue, true); + + int color1; + int color2; + int size; + int size2; + + TypedArray array = null; + try { + array = mGraphView.getContext().obtainStyledAttributes(typedValue.data, new int[]{ + android.R.attr.textColorPrimary + , android.R.attr.textColorSecondary + , android.R.attr.textSize + , android.R.attr.horizontalGap}); + color1 = array.getColor(0, Color.BLACK); + color2 = array.getColor(1, Color.GRAY); + size = array.getDimensionPixelSize(2, 20); + size2 = array.getDimensionPixelSize(3, 20); + array.recycle(); + } catch (Exception e) { + color1 = Color.BLACK; + color2 = Color.GRAY; + size = 20; + size2 = 20; + } + + mStyles.verticalLabelsColor = color1; + mStyles.verticalLabelsSecondScaleColor = color1; + mStyles.horizontalLabelsColor = color1; + mStyles.gridColor = color2; + mStyles.textSize = size; + mStyles.padding = size2; + mStyles.labelsSpace = (int) mStyles.textSize/5; + + mStyles.verticalLabelsAlign = Paint.Align.RIGHT; + mStyles.verticalLabelsSecondScaleAlign = Paint.Align.LEFT; + mStyles.highlightZeroLines = true; + + mStyles.verticalAxisTitleColor = mStyles.verticalLabelsColor; + mStyles.horizontalAxisTitleColor = mStyles.horizontalLabelsColor; + mStyles.verticalAxisTitleTextSize = mStyles.textSize; + mStyles.horizontalAxisTitleTextSize = mStyles.textSize; + + mStyles.horizontalLabelsVisible = true; + mStyles.verticalLabelsVisible = true; + + mStyles.horizontalLabelsAngle = 0f; + + mStyles.gridStyle = GridStyle.BOTH; + reloadStyles(); + } + + /** + * will load the styles to the internal + * paint objects (color, text size, text align) + */ + public void reloadStyles() { + mPaintLine = new Paint(); + mPaintLine.setColor(mStyles.gridColor); + mPaintLine.setStrokeWidth(0); + + mPaintLabel = new Paint(); + mPaintLabel.setTextSize(getTextSize()); + mPaintLabel.setAntiAlias(true); + + mPaintAxisTitle = new Paint(); + mPaintAxisTitle.setTextSize(getTextSize()); + mPaintAxisTitle.setTextAlign(Paint.Align.CENTER); + } + + /** + * GraphView tries to fit the labels + * to display numbers that can be divided by 1, 2, or 5. + * + * By default this is enabled. It makes sense to deactivate it + * when using Dates on the x axis. + + * @return if human rounding is enabled + */ + public boolean isHumanRoundingX() { + return mHumanRoundingX; + } + + /** + * GraphView tries to fit the labels + * to display numbers that can be divided by 1, 2, or 5. + * + * @return if human rounding is enabled + */ + public boolean isHumanRoundingY() { + return mHumanRoundingY; + } + + /** + * activate or deactivate human rounding of the + * horizontal axis. GraphView tries to fit the labels + * to display numbers that can be divided by 1, 2, or 5. + * + * By default this is enabled. It makes sense to deactivate it + * when using Dates on the x axis. + * + * @param humanRoundingX false to deactivate + * @param humanRoundingY false to deactivate + */ + public void setHumanRounding(boolean humanRoundingX, boolean humanRoundingY) { + this.mHumanRoundingX = humanRoundingX; + this.mHumanRoundingY = humanRoundingY; + } + + /** + * activate or deactivate human rounding of the + * horizontal axis. GraphView tries to fit the labels + * to display numbers that can be divided by 1, 2, or 5. + * + * By default this is enabled. + * + * @param humanRoundingBoth false to deactivate on both axises + */ + public void setHumanRounding(boolean humanRoundingBoth) { + this.mHumanRoundingX = humanRoundingBoth; + this.mHumanRoundingY = humanRoundingBoth; + } + + /** + * @return the general text size for the axis titles + */ + public float getTextSize() { + return mStyles.textSize; + } + + /** + * @return the font color of the vertical labels + */ + public int getVerticalLabelsColor() { + return mStyles.verticalLabelsColor; + } + + /** + * @return the alignment of the text of the + * vertical labels + */ + public Paint.Align getVerticalLabelsAlign() { + return mStyles.verticalLabelsAlign; + } + + /** + * @return the font color of the horizontal labels + */ + public int getHorizontalLabelsColor() { + return mStyles.horizontalLabelsColor; + } + + /** + * @return the angle of the horizontal labels + */ + public float getHorizontalLabelsAngle() { + return mStyles.horizontalLabelsAngle; + } + + /** + * clears the internal cache and forces + * to redraw the grid and labels. + * Normally you should always call {@link GraphView#onDataChanged(boolean, boolean)} + * which will call this method. + * + * @param keepLabelsSize true if you don't want + * to recalculate the size of + * the labels. It is recommended + * to use "true" because this will + * improve performance and prevent + * a flickering. + * @param keepViewport true if you don't want that + * the viewport will be recalculated. + * It is recommended to use "true" for + * performance. + */ + public void invalidate(boolean keepLabelsSize, boolean keepViewport) { + if (!keepViewport) { + mIsAdjusted = false; + } + if (!keepLabelsSize) { + if (!mLabelVerticalWidthFixed) { + mLabelVerticalWidth = null; + } + mLabelVerticalHeight = null; + mLabelVerticalSecondScaleWidth = null; + mLabelVerticalSecondScaleHeight = null; + } + //reloadStyles(); + } + + /** + * calculates the vertical steps of + * the second scale. + * This will not do any automatically update + * of the bounds. + * Use always manual bounds for the second scale. + * + * @return true if it is ready + */ + protected boolean adjustVerticalSecondScale() { + if (mLabelHorizontalHeight == null) { + return false; + } + if (mGraphView.mSecondScale == null) { + return true; + } + + double minY = mGraphView.mSecondScale.getMinY(false); + double maxY = mGraphView.mSecondScale.getMaxY(false); + + // TODO find the number of labels + int numVerticalLabels = mNumVerticalLabels; + + double newMinY; + double exactSteps; + + if (mGraphView.mSecondScale.isYAxisBoundsManual()) { + // split range into equal steps + exactSteps = (maxY - minY) / (numVerticalLabels - 1); + + // round because of floating error + exactSteps = Math.round(exactSteps * 1000000d) / 1000000d; + } else { + // TODO auto adjusting + throw new IllegalStateException("Not yet implemented"); + } + + if (mStepsVerticalSecondScale != null && mStepsVerticalSecondScale.size() > 1) { + // else choose other nice steps that previous + // steps are included (divide to have more, or multiplicate to have less) + + double d1 = 0, d2 = 0; + int i = 0; + for (Double v : mStepsVerticalSecondScale.values()) { + if (i == 0) { + d1 = v; + } else { + d2 = v; + break; + } + i++; + } + double oldSteps = d2 - d1; + if (oldSteps > 0) { + double newSteps = Double.NaN; + + if (oldSteps > exactSteps) { + newSteps = oldSteps / 2; + } else if (oldSteps < exactSteps) { + newSteps = oldSteps * 2; + } + + // only if there wont be more than numLabels + // and newSteps will be better than oldSteps + int numStepsOld = (int) ((maxY - minY) / oldSteps); + int numStepsNew = (int) ((maxY - minY) / newSteps); + + boolean shouldChange; + + // avoid switching between 2 steps + if (numStepsOld <= numVerticalLabels && numStepsNew <= numVerticalLabels) { + // both are possible + // only the new if it hows more labels + shouldChange = numStepsNew > numStepsOld; + } else { + shouldChange = true; + } + + if (newSteps != Double.NaN && shouldChange && numStepsNew <= numVerticalLabels) { + exactSteps = newSteps; + } else { + // try to stay to the old steps + exactSteps = oldSteps; + } + } + } else { + // first time + } + + // find the first data point that is relevant to display + // starting from 1st datapoint so that the steps have nice numbers + // goal is to start with the minY or 1 step before + newMinY = mGraphView.getSecondScale().mReferenceY; + // must be down-rounded + double count = Math.floor((minY-newMinY)/exactSteps); + newMinY = count*exactSteps + newMinY; + + // it can happen that we need to add some more labels to fill the complete screen + numVerticalLabels = (int) ((mGraphView.getSecondScale().mCurrentViewport.height()*-1 / exactSteps)) + 2; + + // ensure that the value is valid (minimum 2) + // see https://github.com/appsthatmatter/GraphView/issues/520 + numVerticalLabels = Math.max(numVerticalLabels, 2); + + if (mStepsVerticalSecondScale != null) { + mStepsVerticalSecondScale.clear(); + } else { + mStepsVerticalSecondScale = new LinkedHashMap<>(numVerticalLabels); + } + + int height = mGraphView.getGraphContentHeight(); + // convert data-y to pixel-y in current viewport + double pixelPerData = height / mGraphView.getSecondScale().mCurrentViewport.height()*-1; + + for (int i = 0; i < numVerticalLabels; i++) { + // dont draw if it is top of visible screen + if (newMinY + (i * exactSteps) > mGraphView.getSecondScale().mCurrentViewport.top) { + continue; + } + // dont draw if it is below of visible screen + if (newMinY + (i * exactSteps) < mGraphView.getSecondScale().mCurrentViewport.bottom) { + continue; + } + + + // where is the data point on the current screen + double dataPointPos = newMinY + (i * exactSteps); + double relativeToCurrentViewport = dataPointPos - mGraphView.getSecondScale().mCurrentViewport.bottom; + + double pixelPos = relativeToCurrentViewport * pixelPerData; + mStepsVerticalSecondScale.put((int) pixelPos, dataPointPos); + } + + return true; + } + + /** + * calculates the vertical steps. This will + * automatically change the bounds to nice + * human-readable min/max. + * + * @return true if it is ready + */ + protected boolean adjustVertical(boolean changeBounds) { + if (mLabelHorizontalHeight == null) { + return false; + } + + double minY = mGraphView.getViewport().getMinY(false); + double maxY = mGraphView.getViewport().getMaxY(false); + + if (minY == maxY) { + return false; + } + + // TODO find the number of labels + int numVerticalLabels = mNumVerticalLabels; + + double newMinY; + double exactSteps; + + // split range into equal steps + exactSteps = (maxY - minY) / (numVerticalLabels - 1); + + // round because of floating error + exactSteps = Math.round(exactSteps * 1000000d) / 1000000d; + + // smallest viewport + if (exactSteps == 0d) { + exactSteps = 0.0000001d; + maxY = minY + exactSteps * (numVerticalLabels - 1); + } + + // human rounding to have nice numbers (1, 2, 5, ...) + if (isHumanRoundingY()) { + exactSteps = humanRound(exactSteps, changeBounds); + } else if (mStepsVertical != null && mStepsVertical.size() > 1) { + // else choose other nice steps that previous + // steps are included (divide to have more, or multiplicate to have less) + + double d1 = 0, d2 = 0; + int i = 0; + for (Double v : mStepsVertical.values()) { + if (i == 0) { + d1 = v; + } else { + d2 = v; + break; + } + i++; + } + double oldSteps = d2 - d1; + if (oldSteps > 0) { + double newSteps = Double.NaN; + + if (oldSteps > exactSteps) { + newSteps = oldSteps / 2; + } else if (oldSteps < exactSteps) { + newSteps = oldSteps * 2; + } + + // only if there wont be more than numLabels + // and newSteps will be better than oldSteps + int numStepsOld = (int) ((maxY - minY) / oldSteps); + int numStepsNew = (int) ((maxY - minY) / newSteps); + + boolean shouldChange; + + // avoid switching between 2 steps + if (numStepsOld <= numVerticalLabels && numStepsNew <= numVerticalLabels) { + // both are possible + // only the new if it hows more labels + shouldChange = numStepsNew > numStepsOld; + } else { + shouldChange = true; + } + + if (newSteps != Double.NaN && shouldChange && numStepsNew <= numVerticalLabels) { + exactSteps = newSteps; + } else { + // try to stay to the old steps + exactSteps = oldSteps; + } + } + } else { + // first time + } + + // find the first data point that is relevant to display + // starting from 1st datapoint so that the steps have nice numbers + // goal is to start with the minX or 1 step before + newMinY = mGraphView.getViewport().getReferenceY(); + // must be down-rounded + double count = Math.floor((minY-newMinY)/exactSteps); + newMinY = count*exactSteps + newMinY; + + // now we have our labels bounds + if (changeBounds) { + mGraphView.getViewport().setMinY(newMinY); + mGraphView.getViewport().setMaxY(Math.max(maxY, newMinY + (numVerticalLabels - 1) * exactSteps)); + mGraphView.getViewport().mYAxisBoundsStatus = Viewport.AxisBoundsStatus.AUTO_ADJUSTED; + } + + // it can happen that we need to add some more labels to fill the complete screen + numVerticalLabels = (int) ((mGraphView.getViewport().mCurrentViewport.height()*-1 / exactSteps)) + 2; + + if (mStepsVertical != null) { + mStepsVertical.clear(); + } else { + mStepsVertical = new LinkedHashMap<>((int) numVerticalLabels); + } + + int height = mGraphView.getGraphContentHeight(); + // convert data-y to pixel-y in current viewport + double pixelPerData = height / mGraphView.getViewport().mCurrentViewport.height()*-1; + + for (int i = 0; i < numVerticalLabels; i++) { + // dont draw if it is top of visible screen + if (newMinY + (i * exactSteps) > mGraphView.getViewport().mCurrentViewport.top) { + continue; + } + // dont draw if it is below of visible screen + if (newMinY + (i * exactSteps) < mGraphView.getViewport().mCurrentViewport.bottom) { + continue; + } + + + // where is the data point on the current screen + double dataPointPos = newMinY + (i * exactSteps); + double relativeToCurrentViewport = dataPointPos - mGraphView.getViewport().mCurrentViewport.bottom; + + double pixelPos = relativeToCurrentViewport * pixelPerData; + mStepsVertical.put((int) pixelPos, dataPointPos); + } + + return true; + } + + /** + * calculates the horizontal steps. + * + * @param changeBounds This will automatically change the + * bounds to nice human-readable min/max. + * @return true if it is ready + */ + protected boolean adjustHorizontal(boolean changeBounds) { + if (mLabelVerticalWidth == null) { + return false; + } + + double minX = mGraphView.getViewport().getMinX(false); + double maxX = mGraphView.getViewport().getMaxX(false); + if (minX == maxX) return false; + + // TODO find the number of labels + int numHorizontalLabels = mNumHorizontalLabels; + + double newMinX; + double exactSteps; + + // split range into equal steps + exactSteps = (maxX - minX) / (numHorizontalLabels - 1); + + // round because of floating error + exactSteps = Math.round(exactSteps * 1000000d) / 1000000d; + + // smallest viewport + if (exactSteps == 0d) { + exactSteps = 0.0000001d; + maxX = minX + exactSteps * (numHorizontalLabels - 1); + } + + // human rounding to have nice numbers (1, 2, 5, ...) + if (isHumanRoundingX()) { + exactSteps = humanRound(exactSteps, false); + } else if (mStepsHorizontal != null && mStepsHorizontal.size() > 1) { + // else choose other nice steps that previous + // steps are included (divide to have more, or multiplicate to have less) + + double d1 = 0, d2 = 0; + int i = 0; + for (Double v : mStepsHorizontal.values()) { + if (i == 0) { + d1 = v; + } else { + d2 = v; + break; + } + i++; + } + double oldSteps = d2 - d1; + if (oldSteps > 0) { + double newSteps = Double.NaN; + + if (oldSteps > exactSteps) { + newSteps = oldSteps / 2; + } else if (oldSteps < exactSteps) { + newSteps = oldSteps * 2; + } + + // only if there wont be more than numLabels + // and newSteps will be better than oldSteps + int numStepsOld = (int) ((maxX - minX) / oldSteps); + int numStepsNew = (int) ((maxX - minX) / newSteps); + + boolean shouldChange; + + // avoid switching between 2 steps + if (numStepsOld <= numHorizontalLabels && numStepsNew <= numHorizontalLabels) { + // both are possible + // only the new if it hows more labels + shouldChange = numStepsNew > numStepsOld; + } else { + shouldChange = true; + } + + if (newSteps != Double.NaN && shouldChange && numStepsNew <= numHorizontalLabels) { + exactSteps = newSteps; + } else { + // try to stay to the old steps + exactSteps = oldSteps; + } + } + } else { + // first time + } + + + // starting from 1st datapoint + // goal is to start with the minX or 1 step before + newMinX = mGraphView.getViewport().getReferenceX(); + // must be down-rounded + double count = Math.floor((minX-newMinX)/exactSteps); + newMinX = count*exactSteps + newMinX; + + // now we have our labels bounds + if (changeBounds) { + mGraphView.getViewport().setMinX(newMinX); + mGraphView.getViewport().setMaxX(newMinX + (numHorizontalLabels - 1) * exactSteps); + mGraphView.getViewport().mXAxisBoundsStatus = Viewport.AxisBoundsStatus.AUTO_ADJUSTED; + } + + // it can happen that we need to add some more labels to fill the complete screen + numHorizontalLabels = (int) ((mGraphView.getViewport().mCurrentViewport.width() / exactSteps)) + 1; + + if (mStepsHorizontal != null) { + mStepsHorizontal.clear(); + } else { + mStepsHorizontal = new LinkedHashMap<>((int) numHorizontalLabels); + } + + int width = mGraphView.getGraphContentWidth(); + // convert data-x to pixel-x in current viewport + double pixelPerData = width / mGraphView.getViewport().mCurrentViewport.width(); + + for (int i = 0; i < numHorizontalLabels; i++) { + // dont draw if it is left of visible screen + if (newMinX + (i * exactSteps) < mGraphView.getViewport().mCurrentViewport.left) { + continue; + } + + // where is the data point on the current screen + double dataPointPos = newMinX + (i * exactSteps); + double relativeToCurrentViewport = dataPointPos - mGraphView.getViewport().mCurrentViewport.left; + + double pixelPos = relativeToCurrentViewport * pixelPerData; + mStepsHorizontal.put((int) pixelPos, dataPointPos); + } + + return true; + } + + /** + * adjusts the grid and labels to match to the data + * this will automatically change the bounds to + * nice human-readable values, except the bounds + * are manual. + */ + protected void adjustSteps() { + mIsAdjusted = adjustVertical(! Viewport.AxisBoundsStatus.FIX.equals(mGraphView.getViewport().mYAxisBoundsStatus)); + mIsAdjusted &= adjustVerticalSecondScale(); + mIsAdjusted &= adjustHorizontal(! Viewport.AxisBoundsStatus.FIX.equals(mGraphView.getViewport().mXAxisBoundsStatus)); + } + + /** + * calculates the vertical label size + * @param canvas canvas + */ + protected void calcLabelVerticalSize(Canvas canvas) { + // test label with first and last label + String testLabel = mLabelFormatter.formatLabel(mGraphView.getViewport().getMaxY(false), false); + if (testLabel == null) testLabel = ""; + + Rect textBounds = new Rect(); + mPaintLabel.getTextBounds(testLabel, 0, testLabel.length(), textBounds); + mLabelVerticalWidth = textBounds.width(); + mLabelVerticalHeight = textBounds.height(); + + testLabel = mLabelFormatter.formatLabel(mGraphView.getViewport().getMinY(false), false); + if (testLabel == null) testLabel = ""; + + mPaintLabel.getTextBounds(testLabel, 0, testLabel.length(), textBounds); + mLabelVerticalWidth = Math.max(mLabelVerticalWidth, textBounds.width()); + + // add some pixel to get a margin + mLabelVerticalWidth += 6; + + // space between text and graph content + mLabelVerticalWidth += mStyles.labelsSpace; + + // multiline + int lines = 1; + for (byte c : testLabel.getBytes()) { + if (c == '\n') lines++; + } + mLabelVerticalHeight *= lines; + } + + /** + * calculates the vertical second scale + * label size + * @param canvas canvas + */ + protected void calcLabelVerticalSecondScaleSize(Canvas canvas) { + if (mGraphView.mSecondScale == null) { + mLabelVerticalSecondScaleWidth = 0; + mLabelVerticalSecondScaleHeight = 0; + return; + } + + // test label + double testY = ((mGraphView.mSecondScale.getMaxY(false) - mGraphView.mSecondScale.getMinY(false)) * 0.783) + mGraphView.mSecondScale.getMinY(false); + String testLabel = mGraphView.mSecondScale.getLabelFormatter().formatLabel(testY, false); + Rect textBounds = new Rect(); + mPaintLabel.getTextBounds(testLabel, 0, testLabel.length(), textBounds); + mLabelVerticalSecondScaleWidth = textBounds.width(); + mLabelVerticalSecondScaleHeight = textBounds.height(); + + // multiline + int lines = 1; + for (byte c : testLabel.getBytes()) { + if (c == '\n') lines++; + } + mLabelVerticalSecondScaleHeight *= lines; + } + + /** + * calculates the horizontal label size + * @param canvas canvas + */ + protected void calcLabelHorizontalSize(Canvas canvas) { + // test label + double testX = ((mGraphView.getViewport().getMaxX(false) - mGraphView.getViewport().getMinX(false)) * 0.783) + mGraphView.getViewport().getMinX(false); + String testLabel = mLabelFormatter.formatLabel(testX, true); + if (testLabel == null) { + testLabel = ""; + } + Rect textBounds = new Rect(); + mPaintLabel.getTextBounds(testLabel, 0, testLabel.length(), textBounds); + mLabelHorizontalWidth = textBounds.width(); + + if (!mLabelHorizontalHeightFixed) { + mLabelHorizontalHeight = textBounds.height(); + + // multiline + int lines = 1; + for (byte c : testLabel.getBytes()) { + if (c == '\n') lines++; + } + mLabelHorizontalHeight *= lines; + + mLabelHorizontalHeight = (int) Math.max(mLabelHorizontalHeight, mStyles.textSize); + } + + if (mStyles.horizontalLabelsAngle > 0f && mStyles.horizontalLabelsAngle <= 180f) { + int adjHorizontalHeightH = (int) Math.round(Math.abs(mLabelHorizontalHeight*Math.cos(Math.toRadians(mStyles.horizontalLabelsAngle)))); + int adjHorizontalHeightW = (int) Math.round(Math.abs(mLabelHorizontalWidth*Math.sin(Math.toRadians(mStyles.horizontalLabelsAngle)))); + int adjHorizontalWidthH = (int) Math.round(Math.abs(mLabelHorizontalHeight*Math.sin(Math.toRadians(mStyles.horizontalLabelsAngle)))); + int adjHorizontalWidthW = (int) Math.round(Math.abs(mLabelHorizontalWidth*Math.cos(Math.toRadians(mStyles.horizontalLabelsAngle)))); + + mLabelHorizontalHeight = adjHorizontalHeightH + adjHorizontalHeightW; + mLabelHorizontalWidth = adjHorizontalWidthH + adjHorizontalWidthW; + } + + // space between text and graph content + mLabelHorizontalHeight += mStyles.labelsSpace; + } + + /** + * do the drawing of the grid + * and labels + * @param canvas canvas + */ + public void draw(Canvas canvas) { + boolean labelSizeChanged = false; + if (mLabelHorizontalWidth == null) { + calcLabelHorizontalSize(canvas); + labelSizeChanged = true; + } + if (mLabelVerticalWidth == null) { + calcLabelVerticalSize(canvas); + labelSizeChanged = true; + } + if (mLabelVerticalSecondScaleWidth == null) { + calcLabelVerticalSecondScaleSize(canvas); + labelSizeChanged = true; + } + if (labelSizeChanged) { + // redraw directly + mGraphView.drawGraphElements(canvas); + return; + } + + if (!mIsAdjusted) { + adjustSteps(); + } + + if (mIsAdjusted) { + drawVerticalSteps(canvas); + drawVerticalStepsSecondScale(canvas); + drawHorizontalSteps(canvas); + } else { + // we can not draw anything + return; + } + + drawHorizontalAxisTitle(canvas); + drawVerticalAxisTitle(canvas); + + // draw second scale axis title if it exists + if (mGraphView.mSecondScale != null) { + mGraphView.mSecondScale.drawVerticalAxisTitle(canvas); + } + } + + /** + * draws the horizontal axis title if + * it is set + * @param canvas canvas + */ + protected void drawHorizontalAxisTitle(Canvas canvas) { + if (mHorizontalAxisTitle != null && mHorizontalAxisTitle.length() > 0) { + mPaintAxisTitle.setColor(getHorizontalAxisTitleColor()); + mPaintAxisTitle.setTextSize(getHorizontalAxisTitleTextSize()); + float x = canvas.getWidth() / 2; + float y = canvas.getHeight() - mStyles.padding; + canvas.drawText(mHorizontalAxisTitle, x, y, mPaintAxisTitle); + } + } + + /** + * draws the vertical axis title if + * it is set + * @param canvas canvas + */ + protected void drawVerticalAxisTitle(Canvas canvas) { + if (mVerticalAxisTitle != null && mVerticalAxisTitle.length() > 0) { + mPaintAxisTitle.setColor(getVerticalAxisTitleColor()); + mPaintAxisTitle.setTextSize(getVerticalAxisTitleTextSize()); + float x = getVerticalAxisTitleWidth(); + float y = canvas.getHeight() / 2; + canvas.save(); + canvas.rotate(-90, x, y); + canvas.drawText(mVerticalAxisTitle, x, y, mPaintAxisTitle); + canvas.restore(); + } + } + + /** + * @return the horizontal axis title height + * or 0 if there is no title + */ + public int getHorizontalAxisTitleHeight() { + if (mHorizontalAxisTitle != null && mHorizontalAxisTitle.length() > 0) { + return (int) getHorizontalAxisTitleTextSize(); + } else { + return 0; + } + } + + /** + * @return the vertical axis title width + * or 0 if there is no title + */ + public int getVerticalAxisTitleWidth() { + if (mVerticalAxisTitle != null && mVerticalAxisTitle.length() > 0) { + return (int) getVerticalAxisTitleTextSize(); + } else { + return 0; + } + } + + /** + * draws the horizontal steps + * vertical lines and horizontal labels + * + * @param canvas canvas + */ + protected void drawHorizontalSteps(Canvas canvas) { + // draw horizontal steps (vertical lines and horizontal labels) + mPaintLabel.setColor(getHorizontalLabelsColor()); + int i = 0; + for (Map.Entry e : mStepsHorizontal.entrySet()) { + // draw line + if (mStyles.highlightZeroLines) { + if (e.getValue() == 0d) { + mPaintLine.setStrokeWidth(5); + } else { + mPaintLine.setStrokeWidth(0); + } + } + if (mStyles.gridStyle.drawVertical()) { + // dont draw if it is right of visible screen + if (e.getKey() <= mGraphView.getGraphContentWidth()) { + canvas.drawLine(mGraphView.getGraphContentLeft()+e.getKey(), mGraphView.getGraphContentTop(), mGraphView.getGraphContentLeft()+e.getKey(), mGraphView.getGraphContentTop() + mGraphView.getGraphContentHeight(), mPaintLine); + } + } + + // draw label + if (isHorizontalLabelsVisible()) { + if (mStyles.horizontalLabelsAngle > 0f && mStyles.horizontalLabelsAngle <= 180f) { + if (mStyles.horizontalLabelsAngle < 90f) { + mPaintLabel.setTextAlign((Paint.Align.RIGHT)); + } else if (mStyles.horizontalLabelsAngle <= 180f) { + mPaintLabel.setTextAlign((Paint.Align.LEFT)); + } + } else { + mPaintLabel.setTextAlign(Paint.Align.CENTER); + if (i == mStepsHorizontal.size() - 1) + mPaintLabel.setTextAlign(Paint.Align.RIGHT); + if (i == 0) + mPaintLabel.setTextAlign(Paint.Align.LEFT); + } + + // multiline labels + String label = mLabelFormatter.formatLabel(e.getValue(), true); + if (label == null) { + label = ""; + } + String[] lines = label.split("\n"); + + // If labels are angled, calculate adjustment to line them up with the grid + int labelWidthAdj = 0; + if (mStyles.horizontalLabelsAngle > 0f && mStyles.horizontalLabelsAngle <= 180f) { + Rect textBounds = new Rect(); + mPaintLabel.getTextBounds(lines[0], 0, lines[0].length(), textBounds); + labelWidthAdj = (int) Math.abs(textBounds.width()*Math.cos(Math.toRadians(mStyles.horizontalLabelsAngle))); + } + for (int li = 0; li < lines.length; li++) { + // for the last line y = height + float y = (canvas.getHeight() - mStyles.padding - getHorizontalAxisTitleHeight()) - (lines.length - li - 1) * getTextSize() * 1.1f + mStyles.labelsSpace; + float x = mGraphView.getGraphContentLeft()+e.getKey(); + if (mStyles.horizontalLabelsAngle > 0 && mStyles.horizontalLabelsAngle < 90f) { + canvas.save(); + canvas.rotate(mStyles.horizontalLabelsAngle, x + labelWidthAdj, y); + canvas.drawText(lines[li], x + labelWidthAdj, y, mPaintLabel); + canvas.restore(); + } else if (mStyles.horizontalLabelsAngle > 0 && mStyles.horizontalLabelsAngle <= 180f) { + canvas.save(); + canvas.rotate(mStyles.horizontalLabelsAngle - 180f, x - labelWidthAdj, y); + canvas.drawText(lines[li], x - labelWidthAdj, y, mPaintLabel); + canvas.restore(); + } else { + canvas.drawText(lines[li], x, y, mPaintLabel); + } + } + } + i++; + } + } + + /** + * draws the vertical steps for the + * second scale on the right side + * + * @param canvas canvas + */ + protected void drawVerticalStepsSecondScale(Canvas canvas) { + if (mGraphView.mSecondScale == null) { + return; + } + + // draw only the vertical labels on the right + float startLeft = mGraphView.getGraphContentLeft() + mGraphView.getGraphContentWidth(); + mPaintLabel.setColor(getVerticalLabelsSecondScaleColor()); + mPaintLabel.setTextAlign(getVerticalLabelsSecondScaleAlign()); + for (Map.Entry e : mStepsVerticalSecondScale.entrySet()) { + float posY = mGraphView.getGraphContentTop()+mGraphView.getGraphContentHeight()-e.getKey(); + + // draw label + int labelsWidth = mLabelVerticalSecondScaleWidth; + int labelsOffset = (int) startLeft; + if (getVerticalLabelsSecondScaleAlign() == Paint.Align.RIGHT) { + labelsOffset += labelsWidth; + } else if (getVerticalLabelsSecondScaleAlign() == Paint.Align.CENTER) { + labelsOffset += labelsWidth / 2; + } + + float y = posY; + + String[] lines = mGraphView.mSecondScale.mLabelFormatter.formatLabel(e.getValue(), false).split("\n"); + y += (lines.length * getTextSize() * 1.1f) / 2; // center text vertically + for (int li = 0; li < lines.length; li++) { + // for the last line y = height + float y2 = y - (lines.length - li - 1) * getTextSize() * 1.1f; + canvas.drawText(lines[li], labelsOffset, y2, mPaintLabel); + } + } + } + + /** + * draws the vertical steps + * horizontal lines and vertical labels + * + * @param canvas canvas + */ + protected void drawVerticalSteps(Canvas canvas) { + // draw vertical steps (horizontal lines and vertical labels) + float startLeft = mGraphView.getGraphContentLeft(); + mPaintLabel.setColor(getVerticalLabelsColor()); + mPaintLabel.setTextAlign(getVerticalLabelsAlign()); + + int numberOfLine = mStepsVertical.size(); + int currentLine = 1; + + for (Map.Entry e : mStepsVertical.entrySet()) { + float posY = mGraphView.getGraphContentTop()+mGraphView.getGraphContentHeight()-e.getKey(); + + // draw line + if (mStyles.highlightZeroLines) { + if (e.getValue() == 0d) { + mPaintLine.setStrokeWidth(5); + } else { + mPaintLine.setStrokeWidth(0); + } + } + if (mStyles.gridStyle.drawHorizontal()) { + canvas.drawLine(startLeft, posY, startLeft + mGraphView.getGraphContentWidth(), posY, mPaintLine); + } + + //if draw the label above or below the line, we mustn't draw the first for last label, for beautiful design. + boolean isDrawLabel = true; + if ((mStyles.verticalLabelsVAlign == VerticalLabelsVAlign.ABOVE && currentLine == 1) + || (mStyles.verticalLabelsVAlign == VerticalLabelsVAlign.BELOW && currentLine == numberOfLine)){ + isDrawLabel = false; + } + + // draw label + if (isVerticalLabelsVisible() && isDrawLabel) { + int labelsWidth = mLabelVerticalWidth; + int labelsOffset = 0; + if (getVerticalLabelsAlign() == Paint.Align.RIGHT) { + labelsOffset = labelsWidth; + labelsOffset -= mStyles.labelsSpace; + } else if (getVerticalLabelsAlign() == Paint.Align.CENTER) { + labelsOffset = labelsWidth / 2; + } + labelsOffset += mStyles.padding + getVerticalAxisTitleWidth(); + + float y = posY; + + String label = mLabelFormatter.formatLabel(e.getValue(), false); + if (label == null) { + label = ""; + } + String[] lines = label.split("\n"); + switch (mStyles.verticalLabelsVAlign){ + case MID: + y += (lines.length * getTextSize() * 1.1f) / 2; // center text vertically + break; + case ABOVE: + y -= 5; + break; + case BELOW: + y += (lines.length * getTextSize() * 1.1f) + 5; + break; + } + + for (int li = 0; li < lines.length; li++) { + // for the last line y = height + float y2 = y - (lines.length - li - 1) * getTextSize() * 1.1f; + canvas.drawText(lines[li], labelsOffset, y2, mPaintLabel); + } + } + + currentLine ++; + } + } + + /** + * this will do rounding to generate + * nice human-readable bounds. + * + * @param in the raw value that is to be rounded + * @param roundAlwaysUp true if it shall always round up (ceil) + * @return the rounded number + */ + protected double humanRound(double in, boolean roundAlwaysUp) { + // round-up to 1-steps, 2-steps or 5-steps + int ten = 0; + while (Math.abs(in) >= 10d) { + in /= 10d; + ten++; + } + while (Math.abs(in) < 1d) { + in *= 10d; + ten--; + } + if (roundAlwaysUp) { + if (in == 1d) { + } else if (in <= 2d) { + in = 2d; + } else if (in <= 5d) { + in = 5d; + } else if (in < 10d) { + in = 10d; + } + } else { // always round down + if (in == 1d) { + } else if (in <= 4.9d) { + in = 2d; + } else if (in <= 9.9d) { + in = 5d; + } else if (in < 15d) { + in = 10d; + } + } + return in * Math.pow(10d, ten); + } + + /** + * @return the wrapped styles + */ + public Styles getStyles() { + return mStyles; + } + + /** + * @return the vertical label width + * 0 if there are no vertical labels + */ + public int getLabelVerticalWidth() { + if (mStyles.verticalLabelsVAlign == VerticalLabelsVAlign.ABOVE + || mStyles.verticalLabelsVAlign == VerticalLabelsVAlign.BELOW) { + return 0; + } + return mLabelVerticalWidth == null || !isVerticalLabelsVisible() ? 0 : mLabelVerticalWidth; + } + + /** + * sets a manual and fixed with of the space for + * the vertical labels. This will prevent GraphView to + * calculate the width automatically. + * + * @param width the width of the space for the vertical labels. + * Use null to let GraphView automatically calculate the width. + */ + public void setLabelVerticalWidth(Integer width) { + mLabelVerticalWidth = width; + mLabelVerticalWidthFixed = mLabelVerticalWidth != null; + } + + /** + * @return the horizontal label height + * 0 if there are no horizontal labels + */ + public int getLabelHorizontalHeight() { + return mLabelHorizontalHeight == null || !isHorizontalLabelsVisible() ? 0 : mLabelHorizontalHeight; + } + + /** + * sets a manual and fixed height of the space for + * the horizontal labels. This will prevent GraphView to + * calculate the height automatically. + * + * @param height the height of the space for the horizontal labels. + * Use null to let GraphView automatically calculate the height. + */ + public void setLabelHorizontalHeight(Integer height) { + mLabelHorizontalHeight = height; + mLabelHorizontalHeightFixed = mLabelHorizontalHeight != null; + } + + /** + * @return the grid line color + */ + public int getGridColor() { + return mStyles.gridColor; + } + + /** + * @return whether the line at 0 are highlighted + */ + public boolean isHighlightZeroLines() { + return mStyles.highlightZeroLines; + } + + /** + * @return the padding around the grid and labels + */ + public int getPadding() { + return mStyles.padding; + } + + /** + * @param textSize the general text size of the axis titles. + * can be overwritten with {@link #setVerticalAxisTitleTextSize(float)} + * and {@link #setHorizontalAxisTitleTextSize(float)} + */ + public void setTextSize(float textSize) { + mStyles.textSize = textSize; + reloadStyles(); + } + + /** + * @param verticalLabelsAlign the alignment of the vertical labels + */ + public void setVerticalLabelsAlign(Paint.Align verticalLabelsAlign) { + mStyles.verticalLabelsAlign = verticalLabelsAlign; + } + + /** + * @param verticalLabelsColor the color of the vertical labels + */ + public void setVerticalLabelsColor(int verticalLabelsColor) { + mStyles.verticalLabelsColor = verticalLabelsColor; + } + + /** + * @param horizontalLabelsColor the color of the horizontal labels + */ + public void setHorizontalLabelsColor(int horizontalLabelsColor) { + mStyles.horizontalLabelsColor = horizontalLabelsColor; + } + + /** + * @param horizontalLabelsAngle the angle of the horizontal labels in degrees + */ + public void setHorizontalLabelsAngle(int horizontalLabelsAngle) { + mStyles.horizontalLabelsAngle = horizontalLabelsAngle; + } + + /** + * @param gridColor the color of the grid lines + */ + public void setGridColor(int gridColor) { + mStyles.gridColor = gridColor; + reloadStyles(); + } + + /** + * @param highlightZeroLines flag whether the zero-lines (vertical+ + * horizontal) shall be highlighted + */ + public void setHighlightZeroLines(boolean highlightZeroLines) { + mStyles.highlightZeroLines = highlightZeroLines; + } + + /** + * @param padding the padding around the graph and labels + */ + public void setPadding(int padding) { + mStyles.padding = padding; + } + + /** + * @return the label formatter, that converts + * the raw numbers to strings + */ + public LabelFormatter getLabelFormatter() { + return mLabelFormatter; + } + + /** + * @param mLabelFormatter the label formatter, that converts + * the raw numbers to strings + */ + public void setLabelFormatter(LabelFormatter mLabelFormatter) { + this.mLabelFormatter = mLabelFormatter; + mLabelFormatter.setViewport(mGraphView.getViewport()); + } + + /** + * @return the title of the horizontal axis + */ + public String getHorizontalAxisTitle() { + return mHorizontalAxisTitle; + } + + /** + * @param mHorizontalAxisTitle the title of the horizontal axis + */ + public void setHorizontalAxisTitle(String mHorizontalAxisTitle) { + this.mHorizontalAxisTitle = mHorizontalAxisTitle; + } + + /** + * @return the title of the vertical axis + */ + public String getVerticalAxisTitle() { + return mVerticalAxisTitle; + } + + /** + * @param mVerticalAxisTitle the title of the vertical axis + */ + public void setVerticalAxisTitle(String mVerticalAxisTitle) { + this.mVerticalAxisTitle = mVerticalAxisTitle; + } + + /** + * @return font size of the vertical axis title + */ + public float getVerticalAxisTitleTextSize() { + return mStyles.verticalAxisTitleTextSize; + } + + /** + * @param verticalAxisTitleTextSize font size of the vertical axis title + */ + public void setVerticalAxisTitleTextSize(float verticalAxisTitleTextSize) { + mStyles.verticalAxisTitleTextSize = verticalAxisTitleTextSize; + } + + /** + * @return font color of the vertical axis title + */ + public int getVerticalAxisTitleColor() { + return mStyles.verticalAxisTitleColor; + } + + /** + * @param verticalAxisTitleColor font color of the vertical axis title + */ + public void setVerticalAxisTitleColor(int verticalAxisTitleColor) { + mStyles.verticalAxisTitleColor = verticalAxisTitleColor; + } + + /** + * @return font size of the horizontal axis title + */ + public float getHorizontalAxisTitleTextSize() { + return mStyles.horizontalAxisTitleTextSize; + } + + /** + * @param horizontalAxisTitleTextSize font size of the horizontal axis title + */ + public void setHorizontalAxisTitleTextSize(float horizontalAxisTitleTextSize) { + mStyles.horizontalAxisTitleTextSize = horizontalAxisTitleTextSize; + } + + /** + * @return font color of the horizontal axis title + */ + public int getHorizontalAxisTitleColor() { + return mStyles.horizontalAxisTitleColor; + } + + /** + * @param horizontalAxisTitleColor font color of the horizontal axis title + */ + public void setHorizontalAxisTitleColor(int horizontalAxisTitleColor) { + mStyles.horizontalAxisTitleColor = horizontalAxisTitleColor; + } + + /** + * @return the alignment of the labels on the right side + */ + public Paint.Align getVerticalLabelsSecondScaleAlign() { + return mStyles.verticalLabelsSecondScaleAlign; + } + + /** + * @param verticalLabelsSecondScaleAlign the alignment of the labels on the right side + */ + public void setVerticalLabelsSecondScaleAlign(Paint.Align verticalLabelsSecondScaleAlign) { + mStyles.verticalLabelsSecondScaleAlign = verticalLabelsSecondScaleAlign; + } + + /** + * @return the color of the labels on the right side + */ + public int getVerticalLabelsSecondScaleColor() { + return mStyles.verticalLabelsSecondScaleColor; + } + + /** + * @param verticalLabelsSecondScaleColor the color of the labels on the right side + */ + public void setVerticalLabelsSecondScaleColor(int verticalLabelsSecondScaleColor) { + mStyles.verticalLabelsSecondScaleColor = verticalLabelsSecondScaleColor; + } + + /** + * @return the width of the vertical labels + * of the second scale + */ + public int getLabelVerticalSecondScaleWidth() { + return mLabelVerticalSecondScaleWidth==null?0:mLabelVerticalSecondScaleWidth; + } + + /** + * @return flag whether the horizontal labels are + * visible + */ + public boolean isHorizontalLabelsVisible() { + return mStyles.horizontalLabelsVisible; + } + + /** + * @param horizontalTitleVisible flag whether the horizontal labels are + * visible + */ + public void setHorizontalLabelsVisible(boolean horizontalTitleVisible) { + mStyles.horizontalLabelsVisible = horizontalTitleVisible; + } + + /** + * @return flag whether the vertical labels are + * visible + */ + public boolean isVerticalLabelsVisible() { + return mStyles.verticalLabelsVisible; + } + + /** + * @param verticalTitleVisible flag whether the vertical labels are + * visible + */ + public void setVerticalLabelsVisible(boolean verticalTitleVisible) { + mStyles.verticalLabelsVisible = verticalTitleVisible; + } + + /** + * @return count of the vertical labels, that + * will be shown at one time. + */ + public int getNumVerticalLabels() { + return mNumVerticalLabels; + } + + /** + * @param mNumVerticalLabels count of the vertical labels, that + * will be shown at one time. + */ + public void setNumVerticalLabels(int mNumVerticalLabels) { + this.mNumVerticalLabels = mNumVerticalLabels; + } + + /** + * @return count of the horizontal labels, that + * will be shown at one time. + */ + public int getNumHorizontalLabels() { + return mNumHorizontalLabels; + } + + /** + * @param mNumHorizontalLabels count of the horizontal labels, that + * will be shown at one time. + */ + public void setNumHorizontalLabels(int mNumHorizontalLabels) { + this.mNumHorizontalLabels = mNumHorizontalLabels; + } + + /** + * @return the grid style + */ + public GridStyle getGridStyle() { + return mStyles.gridStyle; + } + + /** + * Define which grid lines shall be drawn + * + * @param gridStyle the grid style + */ + public void setGridStyle(GridStyle gridStyle) { + mStyles.gridStyle = gridStyle; + } + + /** + * @return the space between the labels text and the graph content + */ + public int getLabelsSpace() { + return mStyles.labelsSpace; + } + + /** + * the space between the labels text and the graph content + * + * @param labelsSpace the space between the labels text and the graph content + */ + public void setLabelsSpace(int labelsSpace) { + mStyles.labelsSpace = labelsSpace; + } + + + /** + * set horizontal label align + * @param align + */ + public void setVerticalLabelsVAlign(VerticalLabelsVAlign align){ + mStyles.verticalLabelsVAlign = align; + } + + /** + * Get horizontal label align + * @return align + */ + public VerticalLabelsVAlign getVerticalLabelsVAlign(){ + return mStyles.verticalLabelsVAlign; + } +} diff --git a/src/main/java/com/jjoe64/graphview/LabelFormatter.java b/src/main/java/com/jjoe64/graphview/LabelFormatter.java new file mode 100644 index 000000000..a1f4d9c1e --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/LabelFormatter.java @@ -0,0 +1,51 @@ +/** + * GraphView + * Copyright 2016 Jonas Gehring + * + * 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.jjoe64.graphview; + +/** + * Interface to use as label formatter. + * Implement this in order to generate + * your own labels format. + * It is recommended to override {@link com.jjoe64.graphview.DefaultLabelFormatter}. + * + * @author jjoe64 + */ +public interface LabelFormatter { + /** + * converts a raw number as input to + * a formatted string for the label. + * + * @param value raw input number + * @param isValueX true if it is a value for the x axis + * false if it is a value for the y axis + * @return the formatted number as string + */ + public String formatLabel(double value, boolean isValueX); + + /** + * will be called in order to have a + * reference to the current viewport. + * This is useful if you need the bounds + * to generate your labels. + * You store this viewport in as member variable + * and access it e.g. in the {@link #formatLabel(double, boolean)} + * method. + * + * @param viewport the used viewport + */ + public void setViewport(Viewport viewport); +} diff --git a/src/main/java/com/jjoe64/graphview/LegendRenderer.java b/src/main/java/com/jjoe64/graphview/LegendRenderer.java new file mode 100644 index 000000000..61ea019b2 --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/LegendRenderer.java @@ -0,0 +1,396 @@ +/** + * GraphView + * Copyright 2016 Jonas Gehring + * + * 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.jjoe64.graphview; + +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.RectF; +import android.util.TypedValue; + +import com.jjoe64.graphview.series.Series; + +import java.util.ArrayList; +import java.util.List; + +/** + * The default renderer for the legend box + * + * @author jjoe64 + */ +public class LegendRenderer { + /** + * wrapped styles regarding to the + * legend + */ + private final class Styles { + float textSize; + int spacing; + int padding; + int width; + int backgroundColor; + int textColor; + int margin; + LegendAlign align; + Point fixedPosition; + } + + /** + * alignment of the legend + */ + public enum LegendAlign { + /** + * top right corner + */ + TOP, + + /** + * middle right + */ + MIDDLE, + + /** + * bottom right corner + */ + BOTTOM + } + + /** + * wrapped styles + */ + private Styles mStyles; + + /** + * reference to the graphview + */ + private final GraphView mGraphView; + + /** + * flag whether legend will be + * drawn + */ + private boolean mIsVisible; + + /** + * paint for the drawing + */ + private Paint mPaint; + + /** + * cached legend width + * this will be filled in the drawing. + * Can be cleared via {@link #resetStyles()} + */ + private int cachedLegendWidth; + + /** + * creates legend renderer + * + * @param graphView regarding graphview + */ + public LegendRenderer(GraphView graphView) { + mGraphView = graphView; + mIsVisible = false; + mPaint = new Paint(); + mPaint.setTextAlign(Paint.Align.LEFT); + mStyles = new Styles(); + cachedLegendWidth = 0; + resetStyles(); + } + + /** + * resets the styles to the defaults + * and clears the legend width cache + */ + public void resetStyles() { + mStyles.align = LegendAlign.MIDDLE; + mStyles.textSize = mGraphView.getGridLabelRenderer().getTextSize(); + mStyles.spacing = (int) (mStyles.textSize / 5); + mStyles.padding = (int) (mStyles.textSize / 2); + mStyles.width = 0; + mStyles.backgroundColor = Color.argb(180, 100, 100, 100); + mStyles.margin = (int) (mStyles.textSize / 5); + + // get matching styles from theme + TypedValue typedValue = new TypedValue(); + mGraphView.getContext().getTheme().resolveAttribute(android.R.attr.textAppearanceSmall, typedValue, true); + + int color1; + + try { + TypedArray array = mGraphView.getContext().obtainStyledAttributes(typedValue.data, new int[]{ + android.R.attr.textColorPrimary}); + color1 = array.getColor(0, Color.BLACK); + array.recycle(); + } catch (Exception e) { + color1 = Color.BLACK; + } + + mStyles.textColor = color1; + + cachedLegendWidth = 0; + } + + protected List getAllSeries() { + List allSeries = new ArrayList(); + allSeries.addAll(mGraphView.getSeries()); + if (mGraphView.mSecondScale != null) { + allSeries.addAll(mGraphView.getSecondScale().getSeries()); + } + return allSeries; + } + + /** + * draws the legend if it is visible + * + * @param canvas canvas + * @see #setVisible(boolean) + */ + public void draw(Canvas canvas) { + if (!mIsVisible) return; + + mPaint.setTextSize(mStyles.textSize); + + int shapeSize = (int) (mStyles.textSize*0.8d); + + List allSeries = getAllSeries(); + + // width + int legendWidth = mStyles.width; + if (legendWidth == 0) { + // auto + legendWidth = cachedLegendWidth; + + if (legendWidth == 0) { + Rect textBounds = new Rect(); + for (Series s : allSeries) { + if (s.getTitle() != null) { + mPaint.getTextBounds(s.getTitle(), 0, s.getTitle().length(), textBounds); + legendWidth = Math.max(legendWidth, textBounds.width()); + } + } + if (legendWidth == 0) legendWidth = 1; + + // add shape size + legendWidth += shapeSize+mStyles.padding*2 + mStyles.spacing; + cachedLegendWidth = legendWidth; + } + } + + // rect + float legendHeight = (mStyles.textSize+mStyles.spacing)*allSeries.size() -mStyles.spacing; + float lLeft; + float lTop; + if (mStyles.fixedPosition != null) { + // use fied position + lLeft = mGraphView.getGraphContentLeft() + mStyles.margin + mStyles.fixedPosition.x; + lTop = mGraphView.getGraphContentTop() + mStyles.margin + mStyles.fixedPosition.y; + } else { + lLeft = mGraphView.getGraphContentLeft() + mGraphView.getGraphContentWidth() - legendWidth - mStyles.margin; + switch (mStyles.align) { + case TOP: + lTop = mGraphView.getGraphContentTop() + mStyles.margin; + break; + case MIDDLE: + lTop = mGraphView.getHeight() / 2 - legendHeight / 2; + break; + default: + lTop = mGraphView.getGraphContentTop() + mGraphView.getGraphContentHeight() - mStyles.margin - legendHeight - 2*mStyles.padding; + } + } + float lRight = lLeft+legendWidth; + float lBottom = lTop+legendHeight+2*mStyles.padding; + mPaint.setColor(mStyles.backgroundColor); + canvas.drawRoundRect(new RectF(lLeft, lTop, lRight, lBottom), 8, 8, mPaint); + + int i=0; + for (Series series : allSeries) { + mPaint.setColor(series.getColor()); + canvas.drawRect(new RectF(lLeft+mStyles.padding, lTop+mStyles.padding+(i*(mStyles.textSize+mStyles.spacing)), lLeft+mStyles.padding+shapeSize, lTop+mStyles.padding+(i*(mStyles.textSize+mStyles.spacing))+shapeSize), mPaint); + if (series.getTitle() != null) { + mPaint.setColor(mStyles.textColor); + canvas.drawText(series.getTitle(), lLeft+mStyles.padding+shapeSize+mStyles.spacing, lTop+mStyles.padding+mStyles.textSize+(i*(mStyles.textSize+mStyles.spacing)), mPaint); + } + i++; + } + } + + /** + * @return the flag whether the legend will be drawn + */ + public boolean isVisible() { + return mIsVisible; + } + + /** + * set the flag whether the legend will be drawn + * + * @param mIsVisible visible flag + */ + public void setVisible(boolean mIsVisible) { + this.mIsVisible = mIsVisible; + } + + /** + * @return font size + */ + public float getTextSize() { + return mStyles.textSize; + } + + /** + * sets the font size. this will clear + * the internal legend width cache + * + * @param textSize font size + */ + public void setTextSize(float textSize) { + mStyles.textSize = textSize; + cachedLegendWidth = 0; + } + + /** + * @return the spacing between the text lines + */ + public int getSpacing() { + return mStyles.spacing; + } + + /** + * set the spacing between the text lines + * + * @param spacing the spacing between the text lines + */ + public void setSpacing(int spacing) { + mStyles.spacing = spacing; + } + + /** + * padding is the space between the edge of the box + * and the beginning of the text + * + * @return padding from edge to text + */ + public int getPadding() { + return mStyles.padding; + } + + /** + * padding is the space between the edge of the box + * and the beginning of the text + * + * @param padding padding from edge to text + */ + public void setPadding(int padding) { + mStyles.padding = padding; + } + + /** + * the width of the box exclusive padding + * + * @return the width of the box + * 0 => auto + */ + public int getWidth() { + return mStyles.width; + } + + /** + * the width of the box exclusive padding + * @param width the width of the box exclusive padding + * 0 => auto + */ + public void setWidth(int width) { + mStyles.width = width; + } + + /** + * @return background color of the box + * it is recommended to use semi-transparent + * color. + */ + public int getBackgroundColor() { + return mStyles.backgroundColor; + } + + /** + * @param backgroundColor background color of the box + * it is recommended to use semi-transparent + * color. + */ + public void setBackgroundColor(int backgroundColor) { + mStyles.backgroundColor = backgroundColor; + } + + /** + * @return margin from the edge of the box + * to the corner of the graphview + */ + public int getMargin() { + return mStyles.margin; + } + + /** + * @param margin margin from the edge of the box + * to the corner of the graphview + */ + public void setMargin(int margin) { + mStyles.margin = margin; + } + + /** + * @return the vertical alignment of the box + */ + public LegendAlign getAlign() { + return mStyles.align; + } + + /** + * @param align the vertical alignment of the box + */ + public void setAlign(LegendAlign align) { + mStyles.align = align; + } + + /** + * @return font color + */ + public int getTextColor() { + return mStyles.textColor; + } + + /** + * @param textColor font color + */ + public void setTextColor(int textColor) { + mStyles.textColor = textColor; + } + + /** + * Use fixed coordinates to position the legend. + * This will override the align setting. + * + * @param x x coordinates in pixel + * @param y y coordinates in pixel + */ + public void setFixedPosition(int x, int y) { + mStyles.fixedPosition = new Point(x, y); + } +} diff --git a/src/main/java/com/jjoe64/graphview/RectD.java b/src/main/java/com/jjoe64/graphview/RectD.java new file mode 100644 index 000000000..ebd30fb32 --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/RectD.java @@ -0,0 +1,55 @@ +/** + * GraphView + * Copyright 2016 Jonas Gehring + * + * 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.jjoe64.graphview; + +import android.graphics.RectF; + +/** + * Created by jonas on 05.06.16. + */ +public class RectD { + public double left; + public double right; + public double top; + public double bottom; + + public RectD() { + } + + public RectD(double lLeft, double lTop, double lRight, double lBottom) { + set(lLeft, lTop, lRight, lBottom); + } + + public double width() { + return right-left; + } + + public double height() { + return bottom-top; + } + + public void set(double lLeft, double lTop, double lRight, double lBottom) { + left = lLeft; + right = lRight; + top = lTop; + bottom = lBottom; + } + + public RectF toRectF() { + return new RectF((float) left, (float) top, (float) right, (float) bottom); + } +} diff --git a/src/main/java/com/jjoe64/graphview/SecondScale.java b/src/main/java/com/jjoe64/graphview/SecondScale.java new file mode 100644 index 000000000..6dede5ecf --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/SecondScale.java @@ -0,0 +1,322 @@ +/** + * GraphView + * Copyright 2016 Jonas Gehring + * + * 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.jjoe64.graphview; + +import android.graphics.Canvas; +import android.graphics.Paint; + +import com.jjoe64.graphview.series.Series; + +import java.util.ArrayList; +import java.util.List; + +/** + * To be used to plot a second scale + * on the graph. + * The second scale has always to have + * manual bounds. + * Use {@link #setMinY(double)} and {@link #setMaxY(double)} + * to set them. + * The second scale has it's own array of series. + * + * @author jjoe64 + */ +public class SecondScale { + /** + * reference to the graph + */ + protected final GraphView mGraph; + + /** + * array of series for the second + * scale + */ + protected List mSeries; + + /** + * flag whether the y axis bounds + * are manual. + * For the current version this is always + * true. + */ + private boolean mYAxisBoundsManual = true; + + /** + * + */ + protected RectD mCompleteRange = new RectD(); + + protected RectD mCurrentViewport = new RectD(); + + /** + * label formatter for the y labels + * on the right side + */ + protected LabelFormatter mLabelFormatter; + + protected double mReferenceY = Double.NaN; + + /** + * the paint to draw axis titles + */ + private Paint mPaintAxisTitle; + + /** + * the title of the vertical axis + */ + private String mVerticalAxisTitle; + + /** + * font size of the vertical axis title + */ + public float mVerticalAxisTitleTextSize; + + /** + * font color of the vertical axis title + */ + public int mVerticalAxisTitleColor; + + /** + * creates the second scale. + * normally you do not call this contructor. + * Use {@link com.jjoe64.graphview.GraphView#getSecondScale()} + * in order to get the instance. + */ + SecondScale(GraphView graph) { + mGraph = graph; + mSeries = new ArrayList(); + mLabelFormatter = new DefaultLabelFormatter(); + mLabelFormatter.setViewport(mGraph.getViewport()); + } + + /** + * add a series to the second scale. + * Don't add this series also to the GraphView + * object. + * + * @param s the series + */ + public void addSeries(Series s) { + s.onGraphViewAttached(mGraph); + mSeries.add(s); + mGraph.onDataChanged(false, false); + } + + //public void setYAxisBoundsManual(boolean mYAxisBoundsManual) { + // this.mYAxisBoundsManual = mYAxisBoundsManual; + //} + + /** + * set the min y bounds + * + * @param d min y value + */ + public void setMinY(double d) { + mReferenceY = d; + mCurrentViewport.bottom = d; + } + + /** + * set the max y bounds + * + * @param d max y value + */ + public void setMaxY(double d) { + mCurrentViewport.top = d; + } + + /** + * @return the series of the second scale + */ + public List getSeries() { + return mSeries; + } + + /** + * @return min y bound + */ + public double getMinY(boolean completeRange) { + return completeRange ? mCompleteRange.bottom : mCurrentViewport.bottom; + } + + /** + * @return max y bound + */ + public double getMaxY(boolean completeRange) { + return completeRange ? mCompleteRange.top : mCurrentViewport.top; + } + + /** + * @return always true for the current implementation + */ + public boolean isYAxisBoundsManual() { + return mYAxisBoundsManual; + } + + /** + * @return label formatter for the y labels on the right side + */ + public LabelFormatter getLabelFormatter() { + return mLabelFormatter; + } + + /** + * Set a custom label formatter that is used + * for the y labels on the right side. + * + * @param formatter label formatter for the y labels + */ + public void setLabelFormatter(LabelFormatter formatter) { + mLabelFormatter = formatter; + mLabelFormatter.setViewport(mGraph.getViewport()); + } + + /** + * Removes all series of the graph. + */ + public void removeAllSeries() { + mSeries.clear(); + mGraph.onDataChanged(false, false); + } + + /** + * Remove a specific series of the + * second scale. + * + * @param series + */ + public void removeSeries(Series series) { + mSeries.remove(series); + mGraph.onDataChanged(false, false); + } + + /** + * caches the complete range (minX, maxX, minY, maxY) + * by iterating all series and all datapoints and + * stores it into #mCompleteRange + * + * for the x-range it will respect the series on the + * second scale - not for y-values + */ + public void calcCompleteRange() { + List series = getSeries(); + mCompleteRange.set(0d, 0d, 0d, 0d); + if (!series.isEmpty() && !series.get(0).isEmpty()) { + double d = series.get(0).getLowestValueX(); + for (Series s : series) { + if (!s.isEmpty() && d > s.getLowestValueX()) { + d = s.getLowestValueX(); + } + } + mCompleteRange.left = d; + + d = series.get(0).getHighestValueX(); + for (Series s : series) { + if (!s.isEmpty() && d < s.getHighestValueX()) { + d = s.getHighestValueX(); + } + } + mCompleteRange.right = d; + + if (!series.isEmpty() && !series.get(0).isEmpty()) { + d = series.get(0).getLowestValueY(); + for (Series s : series) { + if (!s.isEmpty() && d > s.getLowestValueY()) { + d = s.getLowestValueY(); + } + } + mCompleteRange.bottom = d; + + d = series.get(0).getHighestValueY(); + for (Series s : series) { + if (!s.isEmpty() && d < s.getHighestValueY()) { + d = s.getHighestValueY(); + } + } + mCompleteRange.top = d; + } + } + } + + /** + * @return the title of the vertical axis + */ + public String getVerticalAxisTitle() { + return mVerticalAxisTitle; + } + + /** + * @param mVerticalAxisTitle the title of the vertical axis + */ + public void setVerticalAxisTitle(String mVerticalAxisTitle) { + if(mPaintAxisTitle==null) { + mPaintAxisTitle = new Paint(); + mPaintAxisTitle.setTextSize(getVerticalAxisTitleTextSize()); + mPaintAxisTitle.setTextAlign(Paint.Align.CENTER); + } + this.mVerticalAxisTitle = mVerticalAxisTitle; + } + + /** + * @return font size of the vertical axis title + */ + public float getVerticalAxisTitleTextSize() { + if (getVerticalAxisTitle() == null || getVerticalAxisTitle().length() == 0) { + return 0; + } + return mVerticalAxisTitleTextSize; + } + + /** + * @param verticalAxisTitleTextSize font size of the vertical axis title + */ + public void setVerticalAxisTitleTextSize(float verticalAxisTitleTextSize) { + mVerticalAxisTitleTextSize = verticalAxisTitleTextSize; + } + + /** + * @return font color of the vertical axis title + */ + public int getVerticalAxisTitleColor() { + return mVerticalAxisTitleColor; + } + + /** + * @param verticalAxisTitleColor font color of the vertical axis title + */ + public void setVerticalAxisTitleColor(int verticalAxisTitleColor) { + mVerticalAxisTitleColor = verticalAxisTitleColor; + } + + /** + * draws the vertical axis title if + * it is set + * @param canvas canvas + */ + protected void drawVerticalAxisTitle(Canvas canvas) { + if (mVerticalAxisTitle != null && mVerticalAxisTitle.length() > 0) { + mPaintAxisTitle.setColor(getVerticalAxisTitleColor()); + mPaintAxisTitle.setTextSize(getVerticalAxisTitleTextSize()); + float x = canvas.getWidth() - getVerticalAxisTitleTextSize()/2; + float y = canvas.getHeight() / 2; + canvas.save(); + canvas.rotate(-90, x, y); + canvas.drawText(mVerticalAxisTitle, x, y, mPaintAxisTitle); + canvas.restore(); + } + } +} diff --git a/src/main/java/com/jjoe64/graphview/UniqueLegendRenderer.java b/src/main/java/com/jjoe64/graphview/UniqueLegendRenderer.java new file mode 100644 index 000000000..d0289bd78 --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/UniqueLegendRenderer.java @@ -0,0 +1,36 @@ +package com.jjoe64.graphview; + +import android.util.Pair; + +import com.jjoe64.graphview.series.Series; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * A LegendRenderer that renders items with the same name and color only once in the legend + * Created by poseidon on 27.02.18. + */ +public class UniqueLegendRenderer extends LegendRenderer { + /** + * creates legend renderer + * + * @param graphView regarding graphview + */ + public UniqueLegendRenderer(GraphView graphView) { + super(graphView); + } + + @Override + protected List getAllSeries() { + List originalSeries = super.getAllSeries(); + List distinctSeries = new ArrayList(); + Set> uniqueSeriesKeys = new HashSet>(); + for(Series series : originalSeries) + if(uniqueSeriesKeys.add(new Pair(series.getColor(), series.getTitle()))) + distinctSeries.add(series); + return distinctSeries; + } +} diff --git a/src/main/java/com/jjoe64/graphview/ValueDependentColor.java b/src/main/java/com/jjoe64/graphview/ValueDependentColor.java new file mode 100644 index 000000000..0d18d5f7e --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/ValueDependentColor.java @@ -0,0 +1,37 @@ +/** + * GraphView + * Copyright 2016 Jonas Gehring + * + * 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.jjoe64.graphview; + +import com.jjoe64.graphview.series.DataPointInterface; + +/** + * you can change the color depending on the value. + * takes only effect for BarGraphSeries. + * + * @see com.jjoe64.graphview.series.BarGraphSeries#setValueDependentColor(ValueDependentColor) + */ +public interface ValueDependentColor { + /** + * this is called when a bar is about to draw + * and the color is be loaded. + * + * @param data the current input value + * @return the color that the bar should be drawn with + * Generate the int via the android.graphics.Color class. + */ + public int get(T data); +} diff --git a/src/main/java/com/jjoe64/graphview/Viewport.java b/src/main/java/com/jjoe64/graphview/Viewport.java new file mode 100644 index 000000000..ce674cce8 --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/Viewport.java @@ -0,0 +1,1353 @@ +/** + * GraphView + * Copyright 2016 Jonas Gehring + * + * 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.jjoe64.graphview; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import androidx.core.view.ViewCompat; +import androidx.core.widget.EdgeEffectCompat; +import android.util.Log; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.widget.OverScroller; + +import com.jjoe64.graphview.series.DataPointInterface; +import com.jjoe64.graphview.series.Series; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * This is the default implementation for the viewport. + * This implementation so for a normal viewport + * where there is a horizontal x-axis and a + * vertical y-axis. + * This viewport is compatible with + * - {@link com.jjoe64.graphview.series.BarGraphSeries} + * - {@link com.jjoe64.graphview.series.LineGraphSeries} + * - {@link com.jjoe64.graphview.series.PointsGraphSeries} + * + * @author jjoe64 + */ +public class Viewport { + /** + * this reference value is used to generate the + * vertical labels. It is used when the y axis bounds + * is set manual and humanRoundingY=false. it will be the minValueY value. + */ + protected double referenceY = Double.NaN; + + /** + * this reference value is used to generate the + * horizontal labels. It is used when the x axis bounds + * is set manual and humanRoundingX=false. it will be the minValueX value. + */ + protected double referenceX = Double.NaN; + + /** + * flag whether the vertical scaling is activated + */ + protected boolean scalableY; + + /** + * minimal viewport used for scaling and scrolling. + * this is used if the data that is available is + * less then the viewport that we want to be able to display. + * + * Double.NaN to disable this value + */ + private RectD mMinimalViewport = new RectD(Double.NaN, Double.NaN, Double.NaN, Double.NaN); + + /** + * the reference number to generate the labels + * @return by default 0, only when manual bounds and no human rounding + * is active, the min x value is returned + */ + protected double getReferenceX() { + // if the bounds is manual then we take the + // original manual min y value as reference + if (isXAxisBoundsManual() && !mGraphView.getGridLabelRenderer().isHumanRoundingX()) { + if (Double.isNaN(referenceX)) { + referenceX = getMinX(false); + } + return referenceX; + } else { + // starting from 0 so that the steps have nice numbers + return 0; + } + } + + /** + * listener to notify when x bounds changed after + * scaling or scrolling. + * This can be used to load more detailed data. + */ + public interface OnXAxisBoundsChangedListener { + /** + * Called after scaling or scrolling with + * the new bounds + * @param minX min x value + * @param maxX max x value + */ + void onXAxisBoundsChanged(double minX, double maxX, OnXAxisBoundsChangedListener.Reason reason); + + public enum Reason { + SCROLL, SCALE + } + } + + /** + * listener for the scale gesture + */ + private final ScaleGestureDetector.OnScaleGestureListener mScaleGestureListener + = new ScaleGestureDetector.OnScaleGestureListener() { + /** + * called by android + * @param detector detector + * @return always true + */ + @Override + public boolean onScale(ScaleGestureDetector detector) { + // --- horizontal scaling --- + double viewportWidth = mCurrentViewport.width(); + + if (mMaxXAxisSize != 0) { + if (viewportWidth > mMaxXAxisSize) { + viewportWidth = mMaxXAxisSize; + } + } + + double center = mCurrentViewport.left + viewportWidth / 2; + + float scaleSpanX; + if (android.os.Build.VERSION.SDK_INT >= 11 && scalableY) { + scaleSpanX = detector.getCurrentSpanX()/detector.getPreviousSpanX(); + } else { + scaleSpanX = detector.getScaleFactor(); + } + + viewportWidth /= scaleSpanX; + mCurrentViewport.left = center - viewportWidth / 2; + mCurrentViewport.right = mCurrentViewport.left+viewportWidth; + + // viewportStart must not be < minX + double minX = getMinX(true); + if (!Double.isNaN(mMinimalViewport.left)) { + minX = Math.min(minX, mMinimalViewport.left); + } + if (mCurrentViewport.left < minX) { + mCurrentViewport.left = minX; + mCurrentViewport.right = mCurrentViewport.left+viewportWidth; + } + + // viewportStart + viewportSize must not be > maxX + double maxX = getMaxX(true); + if (!Double.isNaN(mMinimalViewport.right)) { + maxX = Math.max(maxX, mMinimalViewport.right); + } + if (viewportWidth == 0) { + mCurrentViewport.right = maxX; + } + double overlap = mCurrentViewport.left + viewportWidth - maxX; + if (overlap > 0) { + // scroll left + if (mCurrentViewport.left-overlap > minX) { + mCurrentViewport.left -= overlap; + mCurrentViewport.right = mCurrentViewport.left+viewportWidth; + } else { + // maximal scale + mCurrentViewport.left = minX; + mCurrentViewport.right = maxX; + } + } + + + // --- vertical scaling --- + if (scalableY && android.os.Build.VERSION.SDK_INT >= 11 && detector.getCurrentSpanY() != 0f && detector.getPreviousSpanY() != 0f) { + boolean hasSecondScale = mGraphView.mSecondScale != null; + + double viewportHeight = mCurrentViewport.height()*-1; + + if (mMaxYAxisSize != 0) { + if (viewportHeight > mMaxYAxisSize) { + viewportHeight = mMaxYAxisSize; + } + } + + center = mCurrentViewport.bottom + viewportHeight / 2; + + viewportHeight /= detector.getCurrentSpanY()/detector.getPreviousSpanY(); + mCurrentViewport.bottom = center - viewportHeight / 2; + mCurrentViewport.top = mCurrentViewport.bottom+viewportHeight; + + // ignore bounds when second scale + if (!hasSecondScale) { + // viewportStart must not be < minY + double minY = getMinY(true); + if (!Double.isNaN(mMinimalViewport.bottom)) { + minY = Math.min(minY, mMinimalViewport.bottom); + } + if (mCurrentViewport.bottom < minY) { + mCurrentViewport.bottom = minY; + mCurrentViewport.top = mCurrentViewport.bottom+viewportHeight; + } + + // viewportStart + viewportSize must not be > maxY + double maxY = getMaxY(true); + if (!Double.isNaN(mMinimalViewport.top)) { + maxY = Math.max(maxY, mMinimalViewport.top); + } + if (viewportHeight == 0) { + mCurrentViewport.top = maxY; + } + overlap = mCurrentViewport.bottom + viewportHeight - maxY; + if (overlap > 0) { + // scroll left + if (mCurrentViewport.bottom-overlap > minY) { + mCurrentViewport.bottom -= overlap; + mCurrentViewport.top = mCurrentViewport.bottom+viewportHeight; + } else { + // maximal scale + mCurrentViewport.bottom = minY; + mCurrentViewport.top = maxY; + } + } + } else { + // ---- second scale --- + viewportHeight = mGraphView.mSecondScale.mCurrentViewport.height()*-1; + center = mGraphView.mSecondScale.mCurrentViewport.bottom + viewportHeight / 2; + viewportHeight /= detector.getCurrentSpanY()/detector.getPreviousSpanY(); + mGraphView.mSecondScale.mCurrentViewport.bottom = center - viewportHeight / 2; + mGraphView.mSecondScale.mCurrentViewport.top = mGraphView.mSecondScale.mCurrentViewport.bottom+viewportHeight; + } + } + + // adjustSteps viewport, labels, etc. + mGraphView.onDataChanged(true, false); + + ViewCompat.postInvalidateOnAnimation(mGraphView); + + return true; + } + + /** + * called when scaling begins + * + * @param detector detector + * @return true if it is scalable + */ + @Override + public boolean onScaleBegin(ScaleGestureDetector detector) { + // cursor mode + if (mGraphView.isCursorMode()) { + return false; + } + + if (mIsScalable) { + mScalingActive = true; + return true; + } else { + return false; + } + } + + /** + * called when sacling ends + * This will re-adjustSteps the viewport. + * + * @param detector detector + */ + @Override + public void onScaleEnd(ScaleGestureDetector detector) { + mScalingActive = false; + + // notify + if (mOnXAxisBoundsChangedListener != null) { + mOnXAxisBoundsChangedListener.onXAxisBoundsChanged(getMinX(false), getMaxX(false), OnXAxisBoundsChangedListener.Reason.SCALE); + } + + ViewCompat.postInvalidateOnAnimation(mGraphView); + } + }; + + /** + * simple gesture listener to track scroll events + */ + private final GestureDetector.SimpleOnGestureListener mGestureListener + = new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onDown(MotionEvent e) { + // cursor mode + if (mGraphView.isCursorMode()) { + return true; + } + + if (!mIsScrollable || mScalingActive) return false; + + // Initiates the decay phase of any active edge effects. + releaseEdgeEffects(); + // Aborts any active scroll animations and invalidates. + mScroller.forceFinished(true); + ViewCompat.postInvalidateOnAnimation(mGraphView); + return true; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + // cursor mode + if (mGraphView.isCursorMode()) { + return true; + } + if (!mIsScrollable || mScalingActive) return false; + + // Scrolling uses math based on the viewport (as opposed to math using pixels). + /** + * Pixel offset is the offset in screen pixels, while viewport offset is the + * offset within the current viewport. For additional information on surface sizes + * and pixel offsets, see the docs for {@link computeScrollSurfaceSize()}. For + * additional information about the viewport, see the comments for + * {@link mCurrentViewport}. + */ + double viewportOffsetX = distanceX * mCurrentViewport.width() / mGraphView.getGraphContentWidth(); + double viewportOffsetY = distanceY * mCurrentViewport.height() / mGraphView.getGraphContentHeight(); + + // respect minimal viewport + double completeRangeLeft = mCompleteRange.left; + if (!Double.isNaN(mMinimalViewport.left)) { + completeRangeLeft = Math.min(completeRangeLeft, mMinimalViewport.left); + } + double completeRangeRight = mCompleteRange.right; + if (!Double.isNaN(mMinimalViewport.right)) { + completeRangeRight = Math.max(completeRangeRight, mMinimalViewport.right); + } + double completeRangeWidth = completeRangeRight - completeRangeLeft; + + double completeRangeBottom = mCompleteRange.bottom; + if (!Double.isNaN(mMinimalViewport.bottom)) { + completeRangeBottom = Math.min(completeRangeBottom, mMinimalViewport.bottom); + } + double completeRangeTop = mCompleteRange.top; + if (!Double.isNaN(mMinimalViewport.top)) { + completeRangeTop = Math.max(completeRangeTop, mMinimalViewport.top); + } + double completeRangeHeight = completeRangeTop - completeRangeBottom; + + int completeWidth = (int)((completeRangeWidth/mCurrentViewport.width()) * (double) mGraphView.getGraphContentWidth()); + int completeHeight = (int)((completeRangeHeight/mCurrentViewport.height()) * (double) mGraphView.getGraphContentHeight()); + + int scrolledX = (int) (completeWidth + * (mCurrentViewport.left + viewportOffsetX - completeRangeLeft) + / completeRangeWidth); + + int scrolledY = (int) (completeHeight + * (mCurrentViewport.bottom + viewportOffsetY - completeRangeBottom) + / completeRangeHeight*-1); + boolean canScrollX = mCurrentViewport.left > completeRangeLeft + || mCurrentViewport.right < completeRangeRight; + boolean canScrollY = mCurrentViewport.bottom > completeRangeBottom + || mCurrentViewport.top < completeRangeTop; + + boolean hasSecondScale = mGraphView.mSecondScale != null; + + // second scale + double viewportOffsetY2 = 0d; + if (hasSecondScale) { + viewportOffsetY2 = distanceY * mGraphView.mSecondScale.mCurrentViewport.height() / mGraphView.getGraphContentHeight(); + canScrollY |= mGraphView.mSecondScale.mCurrentViewport.bottom > mGraphView.mSecondScale.mCompleteRange.bottom + || mGraphView.mSecondScale.mCurrentViewport.top < mGraphView.mSecondScale.mCompleteRange.top; + } + + canScrollY &= scrollableY; + + if (canScrollX) { + if (viewportOffsetX < 0) { + double tooMuch = mCurrentViewport.left+viewportOffsetX - completeRangeLeft; + if (tooMuch < 0) { + viewportOffsetX -= tooMuch; + } + } else { + double tooMuch = mCurrentViewport.right+viewportOffsetX - completeRangeRight; + if (tooMuch > 0) { + viewportOffsetX -= tooMuch; + } + } + + mCurrentViewport.left += viewportOffsetX; + mCurrentViewport.right += viewportOffsetX; + + // notify + if (mOnXAxisBoundsChangedListener != null) { + mOnXAxisBoundsChangedListener.onXAxisBoundsChanged(getMinX(false), getMaxX(false), OnXAxisBoundsChangedListener.Reason.SCROLL); + } + } + if (canScrollY) { + // if we have the second axis we ignore the max/min range + if (!hasSecondScale) { + if (viewportOffsetY < 0) { + double tooMuch = mCurrentViewport.bottom+viewportOffsetY - completeRangeBottom; + if (tooMuch < 0) { + viewportOffsetY -= tooMuch; + } + } else { + double tooMuch = mCurrentViewport.top+viewportOffsetY - completeRangeTop; + if (tooMuch > 0) { + viewportOffsetY -= tooMuch; + } + } + } + + mCurrentViewport.top += viewportOffsetY; + mCurrentViewport.bottom += viewportOffsetY; + + // second scale + if (hasSecondScale) { + mGraphView.mSecondScale.mCurrentViewport.top += viewportOffsetY2; + mGraphView.mSecondScale.mCurrentViewport.bottom += viewportOffsetY2; + } + } + + if (canScrollX && scrolledX < 0) { + mEdgeEffectLeft.onPull(scrolledX / (float) mGraphView.getGraphContentWidth()); + } + if (!hasSecondScale && canScrollY && scrolledY < 0) { + mEdgeEffectBottom.onPull(scrolledY / (float) mGraphView.getGraphContentHeight()); + } + if (canScrollX && scrolledX > completeWidth - mGraphView.getGraphContentWidth()) { + mEdgeEffectRight.onPull((scrolledX - completeWidth + mGraphView.getGraphContentWidth()) + / (float) mGraphView.getGraphContentWidth()); + } + if (!hasSecondScale && canScrollY && scrolledY > completeHeight - mGraphView.getGraphContentHeight()) { + mEdgeEffectTop.onPull((scrolledY - completeHeight + mGraphView.getGraphContentHeight()) + / (float) mGraphView.getGraphContentHeight()); + } + + // adjustSteps viewport, labels, etc. + mGraphView.onDataChanged(true, false); + + ViewCompat.postInvalidateOnAnimation(mGraphView); + return true; + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, + float velocityX, float velocityY) { + //fling((int) -velocityX, (int) -velocityY); + return true; + } + }; + + /** + * the state of the axis bounds + */ + public enum AxisBoundsStatus { + /** + * initial means that the bounds gets + * auto adjusted if they are not manual. + * After adjusting the status comes to + * #AUTO_ADJUSTED. + */ + INITIAL, + + /** + * after the bounds got auto-adjusted, + * this status will set. + */ + AUTO_ADJUSTED, + + /** + * means that the bounds are fix (manually) and + * are not to be auto-adjusted. + */ + FIX + } + + /** + * paint to draw background + */ + private Paint mPaint; + + /** + * reference to the graphview + */ + private final GraphView mGraphView; + + /** + * this holds the current visible viewport + * left = minX, right = maxX + * bottom = minY, top = maxY + */ + protected RectD mCurrentViewport = new RectD(); + + /** + * maximum allowed viewport size (horizontal) + * 0 means use the bounds of the actual data that is + * available + */ + protected double mMaxXAxisSize = 0; + + /** + * maximum allowed viewport size (vertical) + * 0 means use the bounds of the actual data that is + * available + */ + protected double mMaxYAxisSize = 0; + + /** + * this holds the whole range of the data + * left = minX, right = maxX + * bottom = minY, top = maxY + */ + protected RectD mCompleteRange = new RectD(); + + /** + * flag whether scaling is currently active + */ + protected boolean mScalingActive; + + /** + * flag whether the viewport is scrollable + */ + private boolean mIsScrollable; + + /** + * flag whether the viewport is scalable + */ + private boolean mIsScalable; + + /** + * flag whether the viewport is scalable + * on the Y axis + */ + private boolean scrollableY; + + /** + * gesture detector to detect scrolling + */ + protected GestureDetector mGestureDetector; + + /** + * detect scaling + */ + protected ScaleGestureDetector mScaleGestureDetector; + + /** + * not used - for fling + */ + protected OverScroller mScroller; + + /** + * not used + */ + private EdgeEffectCompat mEdgeEffectTop; + + /** + * not used + */ + private EdgeEffectCompat mEdgeEffectBottom; + + /** + * glow effect when scrolling left + */ + private EdgeEffectCompat mEdgeEffectLeft; + + /** + * glow effect when scrolling right + */ + private EdgeEffectCompat mEdgeEffectRight; + + /** + * state of the x axis + */ + protected AxisBoundsStatus mXAxisBoundsStatus; + + /** + * state of the y axis + */ + protected AxisBoundsStatus mYAxisBoundsStatus; + + /** + * flag whether the x axis bounds are manual + */ + private boolean mXAxisBoundsManual; + + /** + * flag whether the y axis bounds are manual + */ + private boolean mYAxisBoundsManual; + + /** + * background color of the viewport area + * it is recommended to use a semi-transparent color + */ + private int mBackgroundColor; + + /** + * listener to notify when x bounds changed after + * scaling or scrolling. + * This can be used to load more detailed data. + */ + protected OnXAxisBoundsChangedListener mOnXAxisBoundsChangedListener; + + /** + * optional draw a border between the labels + * and the viewport + */ + private boolean mDrawBorder; + + /** + * color of the border + * @see #setDrawBorder(boolean) + */ + private Integer mBorderColor; + + /** + * custom paint to use for the border + * @see #setDrawBorder(boolean) + */ + private Paint mBorderPaint; + + /** + * creates the viewport + * + * @param graphView graphview + */ + Viewport(GraphView graphView) { + mScroller = new OverScroller(graphView.getContext()); + mEdgeEffectTop = new EdgeEffectCompat(graphView.getContext()); + mEdgeEffectBottom = new EdgeEffectCompat(graphView.getContext()); + mEdgeEffectLeft = new EdgeEffectCompat(graphView.getContext()); + mEdgeEffectRight = new EdgeEffectCompat(graphView.getContext()); + mGestureDetector = new GestureDetector(graphView.getContext(), mGestureListener); + mScaleGestureDetector = new ScaleGestureDetector(graphView.getContext(), mScaleGestureListener); + + mGraphView = graphView; + mXAxisBoundsStatus = AxisBoundsStatus.INITIAL; + mYAxisBoundsStatus = AxisBoundsStatus.INITIAL; + mBackgroundColor = Color.TRANSPARENT; + mPaint = new Paint(); + } + + /** + * will be called on a touch event. + * needed to use scaling and scrolling + * + * @param event + * @return true if it was consumed + */ + public boolean onTouchEvent(MotionEvent event) { + boolean b = mScaleGestureDetector.onTouchEvent(event); + b |= mGestureDetector.onTouchEvent(event); + if (mGraphView.isCursorMode()) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + mGraphView.getCursorMode().onDown(event); + b |= true; + } + if (event.getAction() == MotionEvent.ACTION_MOVE) { + mGraphView.getCursorMode().onMove(event); + b |= true; + } + if (event.getAction() == MotionEvent.ACTION_UP) { + b |= mGraphView.getCursorMode().onUp(event); + } + } + return b; + } + + /** + * change the state of the x axis. + * normally you do not call this method. + * If you want to set manual axis use + * {@link #setXAxisBoundsManual(boolean)} and {@link #setYAxisBoundsManual(boolean)} + * + * @param s state + */ + public void setXAxisBoundsStatus(AxisBoundsStatus s) { + mXAxisBoundsStatus = s; + } + + /** + * change the state of the y axis. + * normally you do not call this method. + * If you want to set manual axis use + * {@link #setXAxisBoundsManual(boolean)} and {@link #setYAxisBoundsManual(boolean)} + * + * @param s state + */ + public void setYAxisBoundsStatus(AxisBoundsStatus s) { + mYAxisBoundsStatus = s; + } + + /** + * @return whether the viewport is scrollable + */ + public boolean isScrollable() { + return mIsScrollable; + } + + /** + * @param mIsScrollable whether is viewport is scrollable + */ + public void setScrollable(boolean mIsScrollable) { + this.mIsScrollable = mIsScrollable; + } + + /** + * @return the x axis state + */ + public AxisBoundsStatus getXAxisBoundsStatus() { + return mXAxisBoundsStatus; + } + + /** + * @return the y axis state + */ + public AxisBoundsStatus getYAxisBoundsStatus() { + return mYAxisBoundsStatus; + } + + /** + * caches the complete range (minX, maxX, minY, maxY) + * by iterating all series and all datapoints and + * stores it into #mCompleteRange + * + * for the x-range it will respect the series on the + * second scale - not for y-values + */ + public void calcCompleteRange() { + List series = mGraphView.getSeries(); + List seriesInclusiveSecondScale = new ArrayList<>(mGraphView.getSeries()); + if (mGraphView.mSecondScale != null) { + seriesInclusiveSecondScale.addAll(mGraphView.mSecondScale.getSeries()); + } + mCompleteRange.set(0d, 0d, 0d, 0d); + if (!seriesInclusiveSecondScale.isEmpty() && !seriesInclusiveSecondScale.get(0).isEmpty()) { + double d = seriesInclusiveSecondScale.get(0).getLowestValueX(); + for (Series s : seriesInclusiveSecondScale) { + if (!s.isEmpty() && d > s.getLowestValueX()) { + d = s.getLowestValueX(); + } + } + mCompleteRange.left = d; + + d = seriesInclusiveSecondScale.get(0).getHighestValueX(); + for (Series s : seriesInclusiveSecondScale) { + if (!s.isEmpty() && d < s.getHighestValueX()) { + d = s.getHighestValueX(); + } + } + mCompleteRange.right = d; + + if (!series.isEmpty() && !series.get(0).isEmpty()) { + d = series.get(0).getLowestValueY(); + for (Series s : series) { + if (!s.isEmpty() && d > s.getLowestValueY()) { + d = s.getLowestValueY(); + } + } + mCompleteRange.bottom = d; + + d = series.get(0).getHighestValueY(); + for (Series s : series) { + if (!s.isEmpty() && d < s.getHighestValueY()) { + d = s.getHighestValueY(); + } + } + mCompleteRange.top = d; + } + } + + // calc current viewport bounds + if (mYAxisBoundsStatus == AxisBoundsStatus.AUTO_ADJUSTED) { + mYAxisBoundsStatus = AxisBoundsStatus.INITIAL; + } + if (mYAxisBoundsStatus == AxisBoundsStatus.INITIAL) { + mCurrentViewport.top = mCompleteRange.top; + mCurrentViewport.bottom = mCompleteRange.bottom; + } + + if (mXAxisBoundsStatus == AxisBoundsStatus.AUTO_ADJUSTED) { + mXAxisBoundsStatus = AxisBoundsStatus.INITIAL; + } + if (mXAxisBoundsStatus == AxisBoundsStatus.INITIAL) { + mCurrentViewport.left = mCompleteRange.left; + mCurrentViewport.right = mCompleteRange.right; + } else if (mXAxisBoundsManual && !mYAxisBoundsManual && mCompleteRange.width() != 0) { + // get highest/lowest of current viewport + // lowest + double d = Double.MAX_VALUE; + for (Series s : series) { + Iterator values = s.getValues(mCurrentViewport.left, mCurrentViewport.right); + while (values.hasNext()) { + double v = values.next().getY(); + if (d > v) { + d = v; + } + } + } + + if (d != Double.MAX_VALUE) { + mCurrentViewport.bottom = d; + } + + // highest + d = Double.MIN_VALUE; + for (Series s : series) { + Iterator values = s.getValues(mCurrentViewport.left, mCurrentViewport.right); + while (values.hasNext()) { + double v = values.next().getY(); + if (d < v) { + d = v; + } + } + } + + if (d != Double.MIN_VALUE) { + mCurrentViewport.top = d; + } + } + + // fixes blank screen when range is zero + if (mCurrentViewport.left == mCurrentViewport.right) mCurrentViewport.right++; + if (mCurrentViewport.top == mCurrentViewport.bottom) mCurrentViewport.top++; + } + + /** + * @param completeRange if true => minX of the complete range of all series + * if false => minX of the current visible viewport + * @return the min x value + */ + public double getMinX(boolean completeRange) { + if (completeRange) { + return mCompleteRange.left; + } else { + return mCurrentViewport.left; + } + } + + /** + * @param completeRange if true => maxX of the complete range of all series + * if false => maxX of the current visible viewport + * @return the max x value + */ + public double getMaxX(boolean completeRange) { + if (completeRange) { + return mCompleteRange.right; + } else { + return mCurrentViewport.right; + } + } + + /** + * @param completeRange if true => minY of the complete range of all series + * if false => minY of the current visible viewport + * @return the min y value + */ + public double getMinY(boolean completeRange) { + if (completeRange) { + return mCompleteRange.bottom; + } else { + return mCurrentViewport.bottom; + } + } + + /** + * @param completeRange if true => maxY of the complete range of all series + * if false => maxY of the current visible viewport + * @return the max y value + */ + public double getMaxY(boolean completeRange) { + if (completeRange) { + return mCompleteRange.top; + } else { + return mCurrentViewport.top; + } + } + + /** + * set the maximal y value for the current viewport. + * Make sure to set the y bounds to manual via + * {@link #setYAxisBoundsManual(boolean)} + * @param y max / highest value + */ + public void setMaxY(double y) { + mCurrentViewport.top = y; + } + + /** + * set the minimal y value for the current viewport. + * Make sure to set the y bounds to manual via + * {@link #setYAxisBoundsManual(boolean)} + * @param y min / lowest value + */ + public void setMinY(double y) { + mCurrentViewport.bottom = y; + } + + /** + * set the maximal x value for the current viewport. + * Make sure to set the x bounds to manual via + * {@link #setXAxisBoundsManual(boolean)} + * @param x max / highest value + */ + public void setMaxX(double x) { + mCurrentViewport.right = x; + } + + /** + * set the minimal x value for the current viewport. + * Make sure to set the x bounds to manual via + * {@link #setXAxisBoundsManual(boolean)} + * @param x min / lowest value + */ + public void setMinX(double x) { + mCurrentViewport.left = x; + } + + /** + * release the glowing effects + */ + private void releaseEdgeEffects() { + mEdgeEffectLeft.onRelease(); + mEdgeEffectRight.onRelease(); + mEdgeEffectTop.onRelease(); + mEdgeEffectBottom.onRelease(); + } + + /** + * not used currently + * + * @param velocityX + * @param velocityY + */ + private void fling(int velocityX, int velocityY) { + velocityY = 0; + releaseEdgeEffects(); + // Flings use math in pixels (as opposed to math based on the viewport). + int maxX = (int)((mCurrentViewport.width()/mCompleteRange.width())*(float)mGraphView.getGraphContentWidth()) - mGraphView.getGraphContentWidth(); + int maxY = (int)((mCurrentViewport.height()/mCompleteRange.height())*(float)mGraphView.getGraphContentHeight()) - mGraphView.getGraphContentHeight(); + int startX = (int)((mCurrentViewport.left - mCompleteRange.left)/mCompleteRange.width())*maxX; + int startY = (int)((mCurrentViewport.top - mCompleteRange.top)/mCompleteRange.height())*maxY; + mScroller.forceFinished(true); + mScroller.fling( + startX, + startY, + velocityX, + velocityY, + 0, maxX, + 0, maxY, + mGraphView.getGraphContentWidth() / 2, + mGraphView.getGraphContentHeight() / 2); + ViewCompat.postInvalidateOnAnimation(mGraphView); + } + + /** + * not used currently + */ + public void computeScroll() { + } + + /** + * Draws the overscroll "glow" at the four edges of the chart region, if necessary. + * + * @see EdgeEffectCompat + */ + private void drawEdgeEffectsUnclipped(Canvas canvas) { + // The methods below rotate and translate the canvas as needed before drawing the glow, + // since EdgeEffectCompat always draws a top-glow at 0,0. + + boolean needsInvalidate = false; + + if (!mEdgeEffectTop.isFinished()) { + final int restoreCount = canvas.save(); + canvas.translate(mGraphView.getGraphContentLeft(), mGraphView.getGraphContentTop()); + mEdgeEffectTop.setSize(mGraphView.getGraphContentWidth(), mGraphView.getGraphContentHeight()); + if (mEdgeEffectTop.draw(canvas)) { + needsInvalidate = true; + } + canvas.restoreToCount(restoreCount); + } + + if (!mEdgeEffectBottom.isFinished()) { + final int restoreCount = canvas.save(); + canvas.translate(mGraphView.getGraphContentLeft(), mGraphView.getGraphContentTop()+mGraphView.getGraphContentHeight()); + canvas.rotate(180, mGraphView.getGraphContentWidth()/2, 0); + mEdgeEffectBottom.setSize(mGraphView.getGraphContentWidth(), mGraphView.getGraphContentHeight()); + if (mEdgeEffectBottom.draw(canvas)) { + needsInvalidate = true; + } + canvas.restoreToCount(restoreCount); + } + + if (!mEdgeEffectLeft.isFinished()) { + final int restoreCount = canvas.save(); + canvas.translate(mGraphView.getGraphContentLeft(), mGraphView.getGraphContentTop()+ mGraphView.getGraphContentHeight()); + canvas.rotate(-90, 0, 0); + mEdgeEffectLeft.setSize(mGraphView.getGraphContentHeight(), mGraphView.getGraphContentWidth()); + if (mEdgeEffectLeft.draw(canvas)) { + needsInvalidate = true; + } + canvas.restoreToCount(restoreCount); + } + + if (!mEdgeEffectRight.isFinished()) { + final int restoreCount = canvas.save(); + canvas.translate(mGraphView.getGraphContentLeft()+ mGraphView.getGraphContentWidth(), mGraphView.getGraphContentTop()); + canvas.rotate(90, 0, 0); + mEdgeEffectRight.setSize(mGraphView.getGraphContentHeight(), mGraphView.getGraphContentWidth()); + if (mEdgeEffectRight.draw(canvas)) { + needsInvalidate = true; + } + canvas.restoreToCount(restoreCount); + } + + if (needsInvalidate) { + ViewCompat.postInvalidateOnAnimation(mGraphView); + } + } + + /** + * will be first called in order to draw + * the canvas + * Used to draw the background + * + * @param c canvas. + */ + public void drawFirst(Canvas c) { + // draw background + if (mBackgroundColor != Color.TRANSPARENT) { + mPaint.setColor(mBackgroundColor); + c.drawRect( + mGraphView.getGraphContentLeft(), + mGraphView.getGraphContentTop(), + mGraphView.getGraphContentLeft()+mGraphView.getGraphContentWidth(), + mGraphView.getGraphContentTop()+mGraphView.getGraphContentHeight(), + mPaint + ); + } + if (mDrawBorder) { + Paint p; + if (mBorderPaint != null) { + p = mBorderPaint; + } else { + p = mPaint; + p.setColor(getBorderColor()); + } + c.drawLine( + mGraphView.getGraphContentLeft(), + mGraphView.getGraphContentTop(), + mGraphView.getGraphContentLeft(), + mGraphView.getGraphContentTop()+mGraphView.getGraphContentHeight(), + p + ); + c.drawLine( + mGraphView.getGraphContentLeft(), + mGraphView.getGraphContentTop()+mGraphView.getGraphContentHeight(), + mGraphView.getGraphContentLeft()+mGraphView.getGraphContentWidth(), + mGraphView.getGraphContentTop()+mGraphView.getGraphContentHeight(), + p + ); + // on the right side if we have second scale + if (mGraphView.mSecondScale != null) { + c.drawLine( + mGraphView.getGraphContentLeft()+mGraphView.getGraphContentWidth(), + mGraphView.getGraphContentTop(), + mGraphView.getGraphContentLeft()+mGraphView.getGraphContentWidth(), + mGraphView.getGraphContentTop()+mGraphView.getGraphContentHeight(), + p + ); + } + } + } + + /** + * draws the glowing edge effect + * + * @param c canvas + */ + public void draw(Canvas c) { + drawEdgeEffectsUnclipped(c); + } + + /** + * @return background of the viewport area + */ + public int getBackgroundColor() { + return mBackgroundColor; + } + + /** + * @param mBackgroundColor background of the viewport area + * use transparent to have no background + */ + public void setBackgroundColor(int mBackgroundColor) { + this.mBackgroundColor = mBackgroundColor; + } + + /** + * @return whether the viewport is scalable + */ + public boolean isScalable() { + return mIsScalable; + } + + /** + * active the scaling/zooming feature + * notice: sets the x axis bounds to manual + * + * @param mIsScalable whether the viewport is scalable + */ + public void setScalable(boolean mIsScalable) { + this.mIsScalable = mIsScalable; + if (mIsScalable) { + mIsScrollable = true; + + // set viewport to manual + setXAxisBoundsManual(true); + } + + } + + /** + * @return whether the x axis bounds are manual. + * @see #setMinX(double) + * @see #setMaxX(double) + */ + public boolean isXAxisBoundsManual() { + return mXAxisBoundsManual; + } + + /** + * @param mXAxisBoundsManual whether the x axis bounds are manual. + * @see #setMinX(double) + * @see #setMaxX(double) + */ + public void setXAxisBoundsManual(boolean mXAxisBoundsManual) { + this.mXAxisBoundsManual = mXAxisBoundsManual; + if (mXAxisBoundsManual) { + mXAxisBoundsStatus = AxisBoundsStatus.FIX; + } + } + + /** + * @return whether the y axis bound are manual + */ + public boolean isYAxisBoundsManual() { + return mYAxisBoundsManual; + } + + /** + * @param mYAxisBoundsManual whether the y axis bounds are manual + * @see #setMaxY(double) + * @see #setMinY(double) + */ + public void setYAxisBoundsManual(boolean mYAxisBoundsManual) { + this.mYAxisBoundsManual = mYAxisBoundsManual; + if (mYAxisBoundsManual) { + mYAxisBoundsStatus = AxisBoundsStatus.FIX; + } + } + + /** + * forces the viewport to scroll to the end + * of the range by keeping the current viewport size. + * + * Important: Only takes effect if x axis bounds are manual. + * + * @see #setXAxisBoundsManual(boolean) + */ + public void scrollToEnd() { + if (mXAxisBoundsManual) { + double size = mCurrentViewport.width(); + mCurrentViewport.right = mCompleteRange.right; + mCurrentViewport.left = mCompleteRange.right - size; + mGraphView.onDataChanged(true, false); + } else { + Log.w("GraphView", "scrollToEnd works only with manual x axis bounds"); + } + } + + /** + * @return the listener when there is one registered. + */ + public OnXAxisBoundsChangedListener getOnXAxisBoundsChangedListener() { + return mOnXAxisBoundsChangedListener; + } + + /** + * set a listener to notify when x bounds changed after + * scaling or scrolling. + * This can be used to load more detailed data. + * + * @param l the listener to use + */ + public void setOnXAxisBoundsChangedListener(OnXAxisBoundsChangedListener l) { + mOnXAxisBoundsChangedListener = l; + } + + /** + * optional draw a border between the labels + * and the viewport + * + * @param drawBorder true to draw the border + */ + public void setDrawBorder(boolean drawBorder) { + this.mDrawBorder = drawBorder; + } + + /** + * the border color used. will be ignored when + * a custom paint is set. + * + * @see #setDrawBorder(boolean) + * @return border color. by default the grid color is used + */ + public int getBorderColor() { + if (mBorderColor != null) { + return mBorderColor; + } + return mGraphView.getGridLabelRenderer().getGridColor(); + } + + /** + * the border color used. will be ignored when + * a custom paint is set. + * + * @param borderColor null to reset + */ + public void setBorderColor(Integer borderColor) { + this.mBorderColor = borderColor; + } + + /** + * custom paint to use for the border. border color + * will be ignored + * + * @see #setDrawBorder(boolean) + * @param borderPaint + */ + public void setBorderPaint(Paint borderPaint) { + this.mBorderPaint = borderPaint; + } + + /** + * activate/deactivate the vertical scrolling + * + * @param scrollableY true to activate + */ + public void setScrollableY(boolean scrollableY) { + this.scrollableY = scrollableY; + } + + /** + * the reference number to generate the labels + * @return by default 0, only when manual bounds and no human rounding + * is active, the min y value is returned + */ + protected double getReferenceY() { + // if the bounds is manual then we take the + // original manual min y value as reference + if (isYAxisBoundsManual() && !mGraphView.getGridLabelRenderer().isHumanRoundingY()) { + if (Double.isNaN(referenceY)) { + referenceY = getMinY(false); + } + return referenceY; + } else { + // starting from 0 so that the steps have nice numbers + return 0; + } + } + + /** + * activate or deactivate the vertical zooming/scaling functionallity. + * This will automatically activate the vertical scrolling and the + * horizontal scaling/scrolling feature. + * + * @param scalableY true to activate + */ + public void setScalableY(boolean scalableY) { + if (scalableY) { + this.scrollableY = true; + setScalable(true); + + if (android.os.Build.VERSION.SDK_INT < 11) { + Log.w("GraphView", "Vertical scaling requires minimum Android 3.0 (API Level 11)"); + } + } + this.scalableY = scalableY; + } + + /** + * maximum allowed viewport size (horizontal) + * 0 means use the bounds of the actual data that is + * available + */ + public double getMaxXAxisSize() { + return mMaxXAxisSize; + } + + /** + * maximum allowed viewport size (vertical) + * 0 means use the bounds of the actual data that is + * available + */ + public double getMaxYAxisSize() { + return mMaxYAxisSize; + } + + /** + * Set the max viewport size (horizontal) + * This can prevent the user from zooming out too much. E.g. with a 24 hours graph, it + * could force the user to only be able to see 2 hours of data at a time. + * Default value is 0 (disabled) + * + * @param mMaxXAxisViewportSize maximum size of viewport + */ + public void setMaxXAxisSize(double mMaxXAxisViewportSize) { + this.mMaxXAxisSize = mMaxXAxisViewportSize; + } + + /** + * Set the max viewport size (vertical) + * This can prevent the user from zooming out too much. E.g. with a 24 hours graph, it + * could force the user to only be able to see 2 hours of data at a time. + * Default value is 0 (disabled) + * + * @param mMaxYAxisViewportSize maximum size of viewport + */ + public void setMaxYAxisSize(double mMaxYAxisViewportSize) { + this.mMaxYAxisSize = mMaxYAxisViewportSize; + } + + /** + * minimal viewport used for scaling and scrolling. + * this is used if the data that is available is + * less then the viewport that we want to be able to display. + * + * if Double.NaN is used, then this value is ignored + * + * @param minX + * @param maxX + * @param minY + * @param maxY + */ + public void setMinimalViewport(double minX, double maxX, double minY, double maxY) { + mMinimalViewport.set(minX, maxY, maxX, minY); + } +} diff --git a/src/main/java/com/jjoe64/graphview/compat/OverScrollerCompat.java b/src/main/java/com/jjoe64/graphview/compat/OverScrollerCompat.java new file mode 100644 index 000000000..b0e73b6da --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/compat/OverScrollerCompat.java @@ -0,0 +1,43 @@ +/** + * GraphView + * Copyright 2016 Jonas Gehring + * + * 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.jjoe64.graphview.compat; + +import android.annotation.TargetApi; +import android.os.Build; +import android.widget.OverScroller; + +/** + * A utility class for using {@link android.widget.OverScroller} in a backward-compatible fashion. + */ +public class OverScrollerCompat { + /** + * Disallow instantiation. + */ + private OverScrollerCompat() { + } + /** + * @see android.view.ScaleGestureDetector#getCurrentSpanY() + */ + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + public static float getCurrVelocity(OverScroller overScroller) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + return overScroller.getCurrVelocity(); + } else { + return 0; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/jjoe64/graphview/helper/DateAsXAxisLabelFormatter.java b/src/main/java/com/jjoe64/graphview/helper/DateAsXAxisLabelFormatter.java new file mode 100644 index 000000000..f429c6cbb --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/helper/DateAsXAxisLabelFormatter.java @@ -0,0 +1,91 @@ +/** + * GraphView + * Copyright 2016 Jonas Gehring + * + * 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.jjoe64.graphview.helper; + +import android.content.Context; + +import com.jjoe64.graphview.DefaultLabelFormatter; + +import java.text.DateFormat; +import java.util.Calendar; +import java.util.Date; + +/** + * Helper class to use date objects as x-values. + * This will use your own Date Format or by default + * the Android default date format to convert + * the x-values (that has to be millis from + * 01-01-1970) into a formatted date string. + * + * See the DateAsXAxis example in the GraphView-Demos project + * to see a working example. + * + * @author jjoe64 + */ +public class DateAsXAxisLabelFormatter extends DefaultLabelFormatter { + /** + * the date format that will convert + * the unix timestamp to string + */ + protected final DateFormat mDateFormat; + + /** + * calendar to avoid creating new date objects + */ + protected final Calendar mCalendar; + + /** + * create the formatter with the Android default date format to convert + * the x-values. + * + * @param context the application context + */ + public DateAsXAxisLabelFormatter(Context context) { + mDateFormat = android.text.format.DateFormat.getDateFormat(context); + mCalendar = Calendar.getInstance(); + } + + /** + * create the formatter with your own custom + * date format to convert the x-values. + * + * @param context the application context + * @param dateFormat custom date format + */ + public DateAsXAxisLabelFormatter(Context context, DateFormat dateFormat) { + mDateFormat = dateFormat; + mCalendar = Calendar.getInstance(); + } + + /** + * formats the x-values as date string. + * + * @param value raw value + * @param isValueX true if it's a x value, otherwise false + * @return value converted to string + */ + @Override + public String formatLabel(double value, boolean isValueX) { + if (isValueX) { + // format as date + mCalendar.setTimeInMillis((long) value); + return mDateFormat.format(mCalendar.getTimeInMillis()); + } else { + return super.formatLabel(value, isValueX); + } + } +} diff --git a/src/main/java/com/jjoe64/graphview/helper/GraphViewXML.java b/src/main/java/com/jjoe64/graphview/helper/GraphViewXML.java new file mode 100644 index 000000000..ba6c8f664 --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/helper/GraphViewXML.java @@ -0,0 +1,134 @@ +/** + * GraphView + * Copyright 2016 Jonas Gehring + * + * 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.jjoe64.graphview.helper; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.util.AttributeSet; +import android.util.Log; + +import com.jjoe64.graphview.GraphView; +import com.jjoe64.graphview.R; +import com.jjoe64.graphview.series.BarGraphSeries; +import com.jjoe64.graphview.series.BaseSeries; +import com.jjoe64.graphview.series.DataPoint; +import com.jjoe64.graphview.series.DataPointInterface; +import com.jjoe64.graphview.series.LineGraphSeries; +import com.jjoe64.graphview.series.PointsGraphSeries; +import com.jjoe64.graphview.series.Series; + +/** + * helper class to use GraphView directly + * in a XML layout file. + * + * You can set the data via attribute app:seriesData + * in the format: "X=Y;X=Y;..." e.g. "0=5.0;1=5;2=4;3=9" + * + * Other styling options: + *
  • app:seriesType="line|bar|points"
  • + *
  • app:seriesColor="#ff0000"
  • + *
  • app:seriesTitle="foobar" - if this is set, the legend will be drawn
  • + *
  • android:title="foobar"
  • + * + * Example: + *
    + * {@code
    + *  
    + * }
    + * 
    + * + * @author jjoe64 + */ +public class GraphViewXML extends GraphView { + /** + * creates the graphview object with data and + * other options from xml attributes. + * + * @param context + * @param attrs + */ + public GraphViewXML(Context context, AttributeSet attrs) { + super(context, attrs); + + // get attributes + TypedArray a=context.obtainStyledAttributes( + attrs, + R.styleable.GraphViewXML); + + String dataStr = a.getString(R.styleable.GraphViewXML_seriesData); + int color = a.getColor(R.styleable.GraphViewXML_seriesColor, Color.TRANSPARENT); + String type = a.getString(R.styleable.GraphViewXML_seriesType); + String seriesTitle = a.getString(R.styleable.GraphViewXML_seriesTitle); + String title = a.getString(R.styleable.GraphViewXML_android_title); + + a.recycle(); + + // decode data + DataPoint[] data; + if (dataStr == null || dataStr.isEmpty()) { + throw new IllegalArgumentException("Attribute seriesData is required in the format: 0=5.0;1=5;2=4;3=9"); + } else { + String[] d = dataStr.split(";"); + try { + data = new DataPoint[d.length]; + int i = 0; + for (String dd : d) { + String[] xy = dd.split("="); + data[i] = new DataPoint(Double.parseDouble(xy[0]), Double.parseDouble(xy[1])); + i++; + } + } catch (Exception e) { + Log.e("GraphViewXML", e.toString()); + throw new IllegalArgumentException("Attribute seriesData is broken. Use this format: 0=5.0;1=5;2=4;3=9"); + } + } + + // create series + BaseSeries series; + if (type == null || type.isEmpty()) { + type = "line"; + } + if (type.equals("line")) { + series = new LineGraphSeries(data); + } else if (type.equals("bar")) { + series = new BarGraphSeries(data); + } else if (type.equals("points")) { + series = new PointsGraphSeries(data); + } else { + throw new IllegalArgumentException("unknown graph type: "+type+". Possible is line|bar|points"); + } + if (color != Color.TRANSPARENT) { + series.setColor(color); + } + addSeries(series); + + if (seriesTitle != null && !seriesTitle.isEmpty()) { + series.setTitle(seriesTitle); + getLegendRenderer().setVisible(true); + } + + if (title != null && !title.isEmpty()) { + setTitle(title); + } + } +} diff --git a/src/main/java/com/jjoe64/graphview/helper/StaticLabelsFormatter.java b/src/main/java/com/jjoe64/graphview/helper/StaticLabelsFormatter.java new file mode 100644 index 000000000..3bb4156ca --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/helper/StaticLabelsFormatter.java @@ -0,0 +1,225 @@ +/** + * GraphView + * Copyright 2016 Jonas Gehring + * + * 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.jjoe64.graphview.helper; + +import com.jjoe64.graphview.DefaultLabelFormatter; +import com.jjoe64.graphview.GraphView; +import com.jjoe64.graphview.LabelFormatter; +import com.jjoe64.graphview.Viewport; + +/** + * Use this label formatter to show static labels. + * Static labels are not bound to the data. It is typical used + * for show text like "low", "middle", "high". + * + * You can set the static labels for vertical or horizontal + * individually and you can define a label formatter that + * is to be used if you don't define static labels. + * + * For example if you only use static labels for horizontal labels, + * graphview will use the dynamicLabelFormatter for the vertical labels. + */ +public class StaticLabelsFormatter implements LabelFormatter { + /** + * reference to the viewport + */ + protected Viewport mViewport; + + /** + * the vertical labels, ordered from bottom to the top + * if it is null, the labels will be generated via the #dynamicLabelFormatter + */ + protected String[] mVerticalLabels; + + /** + * the horizontal labels, ordered form the left to the right + * if it is null, the labels will be generated via the #dynamicLabelFormatter + */ + protected String[] mHorizontalLabels; + + /** + * the label formatter that will format the labels + * for that there are no static labels defined. + */ + protected LabelFormatter mDynamicLabelFormatter; + + /** + * reference to the graphview + */ + protected final GraphView mGraphView; + + /** + * creates the formatter without any static labels + * define your static labels via {@link #setHorizontalLabels(String[])} and {@link #setVerticalLabels(String[])} + * + * @param graphView reference to the graphview + */ + public StaticLabelsFormatter(GraphView graphView) { + mGraphView = graphView; + init(null, null, null); + } + + /** + * creates the formatter without any static labels. + * define your static labels via {@link #setHorizontalLabels(String[])} and {@link #setVerticalLabels(String[])} + * + * @param graphView reference to the graphview + * @param dynamicLabelFormatter the label formatter that will format the labels + * for that there are no static labels defined. + */ + public StaticLabelsFormatter(GraphView graphView, LabelFormatter dynamicLabelFormatter) { + mGraphView = graphView; + init(null, null, dynamicLabelFormatter); + } + + /** + * creates the formatter with static labels defined. + * + * @param graphView reference to the graphview + * @param horizontalLabels the horizontal labels, ordered form the left to the right + * if it is null, the labels will be generated via the #dynamicLabelFormatter + * @param verticalLabels the vertical labels, ordered from bottom to the top + * if it is null, the labels will be generated via the #dynamicLabelFormatter + */ + public StaticLabelsFormatter(GraphView graphView, String[] horizontalLabels, String[] verticalLabels) { + mGraphView = graphView; + init(horizontalLabels, verticalLabels, null); + } + + /** + * creates the formatter with static labels defined. + * + * @param graphView reference to the graphview + * @param horizontalLabels the horizontal labels, ordered form the left to the right + * if it is null, the labels will be generated via the #dynamicLabelFormatter + * @param verticalLabels the vertical labels, ordered from bottom to the top + * if it is null, the labels will be generated via the #dynamicLabelFormatter + * @param dynamicLabelFormatter the label formatter that will format the labels + * for that there are no static labels defined. + */ + public StaticLabelsFormatter(GraphView graphView, String[] horizontalLabels, String[] verticalLabels, LabelFormatter dynamicLabelFormatter) { + mGraphView = graphView; + init(horizontalLabels, verticalLabels, dynamicLabelFormatter); + } + + /** + * @param horizontalLabels the horizontal labels, ordered form the left to the right + * if it is null, the labels will be generated via the #dynamicLabelFormatter + * @param verticalLabels the vertical labels, ordered from bottom to the top + * if it is null, the labels will be generated via the #dynamicLabelFormatter + * @param dynamicLabelFormatter the label formatter that will format the labels + * for that there are no static labels defined. + */ + protected void init(String[] horizontalLabels, String[] verticalLabels, LabelFormatter dynamicLabelFormatter) { + mDynamicLabelFormatter = dynamicLabelFormatter; + if (mDynamicLabelFormatter == null) { + mDynamicLabelFormatter = new DefaultLabelFormatter(); + } + + mHorizontalLabels = horizontalLabels; + mVerticalLabels = verticalLabels; + } + + /** + * Set a label formatter that will be used for the labels + * that don't have static labels. + * + * For example if you only use static labels for horizontal labels, + * graphview will use the dynamicLabelFormatter for the vertical labels. + * + * @param dynamicLabelFormatter the label formatter that will format the labels + * for that there are no static labels defined. + */ + public void setDynamicLabelFormatter(LabelFormatter dynamicLabelFormatter) { + this.mDynamicLabelFormatter = dynamicLabelFormatter; + adjust(); + } + + /** + * @param horizontalLabels the horizontal labels, ordered form the left to the right + * if it is null, the labels will be generated via the #dynamicLabelFormatter + */ + public void setHorizontalLabels(String[] horizontalLabels) { + this.mHorizontalLabels = horizontalLabels; + adjust(); + } + + /** + * @param verticalLabels the vertical labels, ordered from bottom to the top + * if it is null, the labels will be generated via the #dynamicLabelFormatter + */ + public void setVerticalLabels(String[] verticalLabels) { + this.mVerticalLabels = verticalLabels; + adjust(); + } + + /** + * + * @param value raw input number + * @param isValueX true if it is a value for the x axis + * false if it is a value for the y axis + * @return + */ + @Override + public String formatLabel(double value, boolean isValueX) { + if (isValueX && mHorizontalLabels != null) { + double minX = mViewport.getMinX(false); + double maxX = mViewport.getMaxX(false); + double range = maxX - minX; + value = value-minX; + int idx = (int)((value/range) * (mHorizontalLabels.length-1)); + return mHorizontalLabels[idx]; + } else if (!isValueX && mVerticalLabels != null) { + double minY = mViewport.getMinY(false); + double maxY = mViewport.getMaxY(false); + double range = maxY - minY; + value = value-minY; + int idx = (int)((value/range) * (mVerticalLabels.length-1)); + return mVerticalLabels[idx]; + } else { + return mDynamicLabelFormatter.formatLabel(value, isValueX); + } + } + + /** + * @param viewport the used viewport + */ + @Override + public void setViewport(Viewport viewport) { + mViewport = viewport; + adjust(); + } + + /** + * adjusts the number of vertical/horizontal labels + */ + protected void adjust() { + mDynamicLabelFormatter.setViewport(mViewport); + if (mVerticalLabels != null) { + if (mVerticalLabels.length < 2) { + throw new IllegalStateException("You need at least 2 vertical labels if you use static label formatter."); + } + mGraphView.getGridLabelRenderer().setNumVerticalLabels(mVerticalLabels.length); + } + if (mHorizontalLabels != null) { + if (mHorizontalLabels.length < 2) { + throw new IllegalStateException("You need at least 2 horizontal labels if you use static label formatter."); + } + mGraphView.getGridLabelRenderer().setNumHorizontalLabels(mHorizontalLabels.length); + } + } +} diff --git a/src/main/java/com/jjoe64/graphview/series/BarGraphSeries.java b/src/main/java/com/jjoe64/graphview/series/BarGraphSeries.java new file mode 100644 index 000000000..d8ab9ca13 --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/series/BarGraphSeries.java @@ -0,0 +1,526 @@ +/** + * GraphView + * Copyright 2016 Jonas Gehring + * + * 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.jjoe64.graphview.series; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import androidx.core.view.ViewCompat; +import android.util.Log; +import android.view.animation.AccelerateInterpolator; + +import com.jjoe64.graphview.GraphView; +import com.jjoe64.graphview.RectD; +import com.jjoe64.graphview.ValueDependentColor; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * Series with Bars to visualize the data. + * The Bars are always vertical. + * + * @author jjoe64 + */ +public class BarGraphSeries extends BaseSeries { + private static final long ANIMATION_DURATION = 333; + + /** + * paint to do drawing on canvas + */ + private Paint mPaint; + + /** + * custom paint that can be used. + * this will ignore the value dependent color. + */ + private Paint mCustomPaint; + + /** + * spacing between the bars in percentage. + * 0 => no spacing + * 100 => the space between the bars is as big as the bars itself + */ + private int mSpacing; + + /** + * width of a data point + * 0 => no prior knowledge of sampling period, interval between bars will be calculated automatically + * >0 => value is the total distance from one bar to another + */ + private double mDataWidth; + + /** + * callback to generate value-dependent colors + * of the bars + */ + private ValueDependentColor mValueDependentColor; + + /** + * flag whether the values should drawn + * above the bars as text + */ + private boolean mDrawValuesOnTop; + + /** + * color of the text above the bars. + * + * @see #mDrawValuesOnTop + */ + private int mValuesOnTopColor; + + /** + * font size of the text above the bars. + * + * @see #mDrawValuesOnTop + */ + private float mValuesOnTopSize; + + /** + * stores the coordinates of the bars to + * trigger tap on series events. + */ + private Map mDataPoints = new HashMap(); + + /** + * flag for animated rendering + */ + private boolean mAnimated; + + /** + * store the last value that was animated + */ + private double mLastAnimatedValue = Double.NaN; + + /** + * time of start animation + */ + private long mAnimationStart; + + /** + * animation interpolator + */ + private AccelerateInterpolator mAnimationInterpolator; + + /** + * frame number of animation to avoid lagging + */ + private int mAnimationStartFrameNo; + + + /** + * creates bar series without any data + */ + public BarGraphSeries() { + mPaint = new Paint(); + } + + /** + * creates bar series with data + * + * @param data data points + * important: array has to be sorted from lowest x-value to the highest + */ + public BarGraphSeries(E[] data) { + super(data); + mPaint = new Paint(); + mAnimationInterpolator = new AccelerateInterpolator(2f); + } + + /** + * draws the bars on the canvas + * + * @param graphView corresponding graphview + * @param canvas canvas + * @param isSecondScale whether we are plotting the second scale or not + */ + @Override + public void draw(GraphView graphView, Canvas canvas, boolean isSecondScale) { + mPaint.setTextAlign(Paint.Align.CENTER); + if (mValuesOnTopSize == 0) { + mValuesOnTopSize = graphView.getGridLabelRenderer().getTextSize(); + } + mPaint.setTextSize(mValuesOnTopSize); + + resetDataPoints(); + + // get data + double maxX = graphView.getViewport().getMaxX(false); + double minX = graphView.getViewport().getMinX(false); + + double maxY; + double minY; + if (isSecondScale) { + maxY = graphView.getSecondScale().getMaxY(false); + minY = graphView.getSecondScale().getMinY(false); + } else { + maxY = graphView.getViewport().getMaxY(false); + minY = graphView.getViewport().getMinY(false); + } + + // Iterate through all bar graph series + // so we know how wide to make our bar, + // and in what position to put it in + int numBarSeries = 0; + int currentSeriesOrder = 0; + int numValues = 0; + boolean isCurrentSeries; + SortedSet xVals = new TreeSet(); + for(Series inspectedSeries: graphView.getSeries()) { + if(inspectedSeries instanceof BarGraphSeries) { + isCurrentSeries = (inspectedSeries == this); + if(isCurrentSeries) { + currentSeriesOrder = numBarSeries; + } + numBarSeries++; + + // calculate the number of slots for bars based on the minimum distance between + // x coordinates in the series. This is divided into the range to find + // the placement and width of bar slots + // (sections of the x axis for each bar or set of bars) + // TODO: Move this somewhere more general and cache it, so we don't recalculate it for each series + Iterator curValues = inspectedSeries.getValues(minX, maxX); + if (curValues.hasNext()) { + xVals.add(curValues.next().getX()); + if(isCurrentSeries) { numValues++; } + while (curValues.hasNext()) { + xVals.add(curValues.next().getX()); + if(isCurrentSeries) { numValues++; } + } + } + } + } + if (numValues == 0) { + return; + } + + double minGap = 0; + + if(mDataWidth > 0.0) { + minGap = mDataWidth; + } else { + Double lastVal = null; + + for(Double curVal: xVals) { + if(lastVal != null) { + double curGap = Math.abs(curVal - lastVal); + if (minGap == 0 || (curGap > 0 && curGap < minGap)) { + minGap = curGap; + } + } + lastVal = curVal; + } + } + + int numBarSlots = (minGap == 0) ? 1 : (int)Math.round((maxX - minX)/minGap) + 1; + + Iterator values = getValues(minX, maxX); + + // Calculate the overall bar slot width - this includes all bars across + // all series, and any spacing between sets of bars + int barSlotWidth = numBarSlots == 1 + ? graphView.getGraphContentWidth() + : graphView.getGraphContentWidth() / (numBarSlots-1); + + // Total spacing (both sides) between sets of bars + double spacing = Math.min(barSlotWidth*mSpacing/100, barSlotWidth*0.98f); + // Width of an individual bar + double barWidth = (barSlotWidth - spacing) / numBarSeries; + // Offset from the center of a given bar to start drawing + double offset = barSlotWidth/2; + + double diffY = maxY - minY; + double diffX = maxX - minX; + double contentHeight = graphView.getGraphContentHeight(); + double contentWidth = graphView.getGraphContentWidth(); + double contentLeft = graphView.getGraphContentLeft(); + double contentTop = graphView.getGraphContentTop(); + + // draw data + int i=0; + while (values.hasNext()) { + E value = values.next(); + + double valY = value.getY() - minY; + double ratY = valY / diffY; + double y = contentHeight * ratY; + + double valY0 = 0 - minY; + double ratY0 = valY0 / diffY; + double y0 = contentHeight * ratY0; + + double valueX = value.getX(); + double valX = valueX - minX; + double ratX = valX / diffX; + double x = contentWidth * ratX; + + // hook for value dependent color + if (getValueDependentColor() != null) { + mPaint.setColor(getValueDependentColor().get(value)); + } else { + mPaint.setColor(getColor()); + } + + double left = x + contentLeft - offset + spacing/2 + currentSeriesOrder*barWidth; + double right = left + barWidth; + if (left > contentLeft + contentWidth || right < contentLeft) { + continue; + } + double top = (contentTop - y) + contentHeight; + double bottom = (contentTop - y0) + contentHeight - (graphView.getGridLabelRenderer().isHighlightZeroLines()?4:1); + + boolean reverse = top > bottom; + + if (mAnimated) { + if ((Double.isNaN(mLastAnimatedValue) || mLastAnimatedValue < valueX)) { + long currentTime = System.currentTimeMillis(); + if (mAnimationStart == 0) { + // start animation + mAnimationStart = currentTime; + mAnimationStartFrameNo = 0; + } else { + // anti-lag: wait a few frames + if (mAnimationStartFrameNo < 15) { + // second time + mAnimationStart = currentTime; + mAnimationStartFrameNo++; + } + } + float timeFactor = (float) (currentTime-mAnimationStart) / ANIMATION_DURATION; + float factor = mAnimationInterpolator.getInterpolation(timeFactor); + if (timeFactor <= 1.0) { + double barHeight = bottom - top; + barHeight = barHeight * factor; + top = bottom-barHeight; + ViewCompat.postInvalidateOnAnimation(graphView); + } else { + // animation finished + mLastAnimatedValue = valueX; + } + } + } + + if (reverse) { + double tmp = top; + top = bottom + (graphView.getGridLabelRenderer().isHighlightZeroLines()?4:1); + bottom = tmp; + } + + // overdraw + left = Math.max(left, contentLeft); + right = Math.min(right, contentLeft+contentWidth); + bottom = Math.min(bottom, contentTop+contentHeight); + top = Math.max(top, contentTop); + + mDataPoints.put(new RectD(left, top, right, bottom), value); + + Paint p; + if (mCustomPaint != null) { + p = mCustomPaint; + } else { + p = mPaint; + } + canvas.drawRect((float)left, (float)top, (float)right, (float)bottom, p); + + // set values on top of graph + if (mDrawValuesOnTop) { + if (reverse) { + top = bottom + mValuesOnTopSize + 4; + if (top > contentTop+contentHeight) top = contentTop + contentHeight; + } else { + top -= 4; + if (top<=contentTop) top+=contentTop+4; + } + + mPaint.setColor(mValuesOnTopColor); + canvas.drawText( + graphView.getGridLabelRenderer().getLabelFormatter().formatLabel(value.getY(), false) + , (float) (left+right)/2, (float) top, mPaint); + } + + i++; + } + } + + /** + * @return the hook to generate value-dependent color. default null + */ + public ValueDependentColor getValueDependentColor() { + return mValueDependentColor; + } + + /** + * set a hook to make the color of the bars depending + * on the actually value/data. + * + * @param mValueDependentColor hook + * null to disable + */ + public void setValueDependentColor(ValueDependentColor mValueDependentColor) { + this.mValueDependentColor = mValueDependentColor; + } + + /** + * @return the spacing between the bars in percentage + */ + public int getSpacing() { + return mSpacing; + } + + /** + * @param mSpacing spacing between the bars in percentage. + * 0 => no spacing + * 100 => the space between the bars is as big as the bars itself + */ + public void setSpacing(int mSpacing) { + this.mSpacing = mSpacing; + } + + /** + * @return the interval between data points + */ + public double getDataWidth() { + return mDataWidth; + } + + /** + * @param mDataWidth width of a data point (sampling period) + * 0 => no prior knowledge of sampling period, interval between bars will be calculated automatically + * >0 => value is the total distance from one bar to another + */ + public void setDataWidth(double mDataWidth) { + this.mDataWidth = mDataWidth; + } + + /** + * @return whether the values should be drawn above the bars + */ + public boolean isDrawValuesOnTop() { + return mDrawValuesOnTop; + } + + /** + * @param mDrawValuesOnTop flag whether the values should drawn + * above the bars as text + */ + public void setDrawValuesOnTop(boolean mDrawValuesOnTop) { + this.mDrawValuesOnTop = mDrawValuesOnTop; + } + + /** + * @return font color of the values on top of the bars + * @see #setDrawValuesOnTop(boolean) + */ + public int getValuesOnTopColor() { + return mValuesOnTopColor; + } + + /** + * @param mValuesOnTopColor the font color of the values on top of the bars + * @see #setDrawValuesOnTop(boolean) + */ + public void setValuesOnTopColor(int mValuesOnTopColor) { + this.mValuesOnTopColor = mValuesOnTopColor; + } + + /** + * @return font size of the values above the bars + * @see #setDrawValuesOnTop(boolean) + */ + public float getValuesOnTopSize() { + return mValuesOnTopSize; + } + + /** + * @param mValuesOnTopSize font size of the values above the bars + * @see #setDrawValuesOnTop(boolean) + */ + public void setValuesOnTopSize(float mValuesOnTopSize) { + this.mValuesOnTopSize = mValuesOnTopSize; + } + + /** + * resets the cached coordinates of the bars + */ + @Override + protected void resetDataPoints() { + mDataPoints.clear(); + } + + /** + * find the corresponding data point by + * coordinates. + * + * @param x pixels + * @param y pixels + * @return datapoint or null + */ + @Override + protected E findDataPoint(float x, float y) { + for (Map.Entry entry : mDataPoints.entrySet()) { + if (x >= entry.getKey().left && x <= entry.getKey().right + && y >= entry.getKey().top && y <= entry.getKey().bottom) { + return entry.getValue(); + } + } + return null; + } + + /** + * custom paint that can be used. + * this will ignore the value dependent color. + * + * @return custom paint or null + */ + public Paint getCustomPaint() { + return mCustomPaint; + } + + /** + * custom paint that can be used. + * this will ignore the value dependent color. + * + * @param mCustomPaint custom paint to use or null + */ + public void setCustomPaint(Paint mCustomPaint) { + this.mCustomPaint = mCustomPaint; + } + + /** + * draw the series with an animation + * + * @param animated animation activated or not + */ + public void setAnimated(boolean animated) { + this.mAnimated = animated; + } + + /** + * @return rendering is animated or not + */ + public boolean isAnimated() { + return mAnimated; + } + + @Override + public void drawSelection(GraphView mGraphView, Canvas canvas, boolean b, DataPointInterface value) { + // TODO + } +} diff --git a/src/main/java/com/jjoe64/graphview/series/BaseSeries.java b/src/main/java/com/jjoe64/graphview/series/BaseSeries.java new file mode 100644 index 000000000..141b7497d --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/series/BaseSeries.java @@ -0,0 +1,557 @@ +/** + * GraphView + * Copyright 2016 Jonas Gehring + * + * 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.jjoe64.graphview.series; + +import android.graphics.Canvas; +import android.graphics.PointF; +import android.util.Log; + +import com.jjoe64.graphview.GraphView; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * Basis implementation for series. + * Used for series that are plotted on + * a default x/y 2d viewport. + * + * Extend this class to implement your own custom + * graph type. + * + * This implementation uses a internal Array to store + * the data. If you want to implement a custom data provider + * you may want to implement {@link com.jjoe64.graphview.series.Series}. + * + * @author jjoe64 + */ +public abstract class BaseSeries implements Series { + /** + * holds the data + */ + final private List mData = new ArrayList(); + + /** + * stores the used coordinates to find the + * corresponding data point on a tap + * + * Key => x/y pixel + * Value => Plotted Datapoint + * + * will be filled while drawing via {@link #registerDataPoint(float, float, DataPointInterface)} + */ + private Map mDataPoints = new HashMap(); + + /** + * title for this series that can be displayed + * in the legend. + */ + private String mTitle; + + /** + * base color for this series. will be used also in + * the legend + */ + private int mColor = 0xff0077cc; + + /** + * cache for lowest y value + */ + private double mLowestYCache = Double.NaN; + + /** + * cahce for highest y value + */ + private double mHighestYCache = Double.NaN; + + /** + * listener to handle tap events on a data point + */ + protected OnDataPointTapListener mOnDataPointTapListener; + + /** + * stores the graphviews where this series is used. + * Can be more than one. + */ + private List> mGraphViews; + private Boolean mIsCursorModeCache; + + /** + * creates series without data + */ + public BaseSeries() { + mGraphViews = new ArrayList<>(); + } + + /** + * creates series with data + * + * @param data data points + * important: array has to be sorted from lowest x-value to the highest + */ + public BaseSeries(E[] data) { + mGraphViews = new ArrayList<>(); + for (E d : data) { + mData.add(d); + } + checkValueOrder(null); + } + + /** + * @return the lowest x value, or 0 if there is no data + */ + public double getLowestValueX() { + if (mData.isEmpty()) return 0d; + return mData.get(0).getX(); + } + + /** + * @return the highest x value, or 0 if there is no data + */ + public double getHighestValueX() { + if (mData.isEmpty()) return 0d; + return mData.get(mData.size()-1).getX(); + } + + /** + * @return the lowest y value, or 0 if there is no data + */ + public double getLowestValueY() { + if (mData.isEmpty()) return 0d; + if (!Double.isNaN(mLowestYCache)) { + return mLowestYCache; + } + double l = mData.get(0).getY(); + for (int i = 1; i < mData.size(); i++) { + double c = mData.get(i).getY(); + if (l > c) { + l = c; + } + } + return mLowestYCache = l; + } + + /** + * @return the highest y value, or 0 if there is no data + */ + public double getHighestValueY() { + if (mData.isEmpty()) return 0d; + if (!Double.isNaN(mHighestYCache)) { + return mHighestYCache; + } + double h = mData.get(0).getY(); + for (int i = 1; i < mData.size(); i++) { + double c = mData.get(i).getY(); + if (h < c) { + h = c; + } + } + return mHighestYCache = h; + } + + /** + * get the values for a given x range. if from and until are bigger or equal than + * all the data, the original data is returned. + * If it is only a part of the data, the range is returned plus one datapoint + * before and after to get a nice scrolling. + * + * @param from minimal x-value + * @param until maximal x-value + * @return data for the range +/- 1 datapoint + */ + @Override + public Iterator getValues(final double from, final double until) { + if (from <= getLowestValueX() && until >= getHighestValueX()) { + return mData.iterator(); + } else { + return new Iterator() { + Iterator org = mData.iterator(); + E nextValue = null; + E nextNextValue = null; + boolean plusOne = true; + + { + // go to first + boolean found = false; + E prevValue = null; + if (org.hasNext()) { + prevValue = org.next(); + } + if (prevValue != null) { + if (prevValue.getX() >= from) { + nextValue = prevValue; + found = true; + } else { + while (org.hasNext()) { + nextValue = org.next(); + if (nextValue.getX() >= from) { + found = true; + nextNextValue = nextValue; + nextValue = prevValue; + break; + } + prevValue = nextValue; + } + } + } + if (!found) { + nextValue = null; + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public E next() { + if (hasNext()) { + E r = nextValue; + if (r.getX() > until) { + plusOne = false; + } + if (nextNextValue != null) { + nextValue = nextNextValue; + nextNextValue = null; + } else if (org.hasNext()) nextValue = org.next(); + else nextValue = null; + return r; + } else { + throw new NoSuchElementException(); + } + } + + @Override + public boolean hasNext() { + return nextValue != null && (nextValue.getX() <= until || plusOne); + } + }; + } + } + + /** + * @return the title of the series + */ + public String getTitle() { + return mTitle; + } + + /** + * set the title of the series. This will be used in + * the legend. + * + * @param mTitle title of the series + */ + public void setTitle(String mTitle) { + this.mTitle = mTitle; + } + + /** + * @return color of the series + */ + public int getColor() { + return mColor; + } + + /** + * set the color of the series. This will be used in + * plotting (depends on the series implementation) and + * is used in the legend. + * + * @param mColor + */ + public void setColor(int mColor) { + this.mColor = mColor; + } + + /** + * set a listener for tap on a data point. + * + * @param l listener + */ + public void setOnDataPointTapListener(OnDataPointTapListener l) { + this.mOnDataPointTapListener = l; + } + + /** + * called by the tap detector in order to trigger + * the on tap on datapoint event. + * + * @param x pixel + * @param y pixel + */ + @Override + public void onTap(float x, float y) { + if (mOnDataPointTapListener != null) { + E p = findDataPoint(x, y); + if (p != null) { + mOnDataPointTapListener.onTap(this, p); + } + } + } + + /** + * find the data point which is next to the + * coordinates + * + * @param x pixel + * @param y pixel + * @return the data point or null if nothing was found + */ + protected E findDataPoint(float x, float y) { + float shortestDistance = Float.NaN; + E shortest = null; + for (Map.Entry entry : mDataPoints.entrySet()) { + float x1 = entry.getKey().x; + float y1 = entry.getKey().y; + float x2 = x; + float y2 = y; + + float distance = (float) Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)); + if (shortest == null || distance < shortestDistance) { + shortestDistance = distance; + shortest = entry.getValue(); + } + } + if (shortest != null) { + if (shortestDistance < 120) { + return shortest; + } + } + return null; + } + + public E findDataPointAtX(float x) { + float shortestDistance = Float.NaN; + E shortest = null; + for (Map.Entry entry : mDataPoints.entrySet()) { + float x1 = entry.getKey().x; + float x2 = x; + + float distance = Math.abs(x1 - x2); + if (shortest == null || distance < shortestDistance) { + shortestDistance = distance; + shortest = entry.getValue(); + } + } + if (shortest != null) { + if (shortestDistance < 200) { + return shortest; + } + } + return null; + } + + /** + * register the datapoint to find it at a tap + * + * @param x pixel + * @param y pixel + * @param dp the data point to save + */ + protected void registerDataPoint(float x, float y, E dp) { + // performance + // TODO maybe invalidate after setting the listener + if (mOnDataPointTapListener != null || isCursorMode()) { + mDataPoints.put(new PointF(x, y), dp); + } + } + + private boolean isCursorMode() { + if (mIsCursorModeCache != null) { + return mIsCursorModeCache; + } + for (WeakReference graphView : mGraphViews) { + if (graphView != null && graphView.get() != null && graphView.get().isCursorMode()) { + return mIsCursorModeCache = true; + } + } + return mIsCursorModeCache = false; + } + + /** + * clears the cached data point coordinates + */ + protected void resetDataPoints() { + mDataPoints.clear(); + } + + /** + * clears the data of this series and sets new. + * will redraw the graph + * + * @param data the values must be in the correct order! + * x-value has to be ASC. First the lowest x value and at least the highest x value. + */ + public void resetData(E[] data) { + mData.clear(); + for (E d : data) { + mData.add(d); + } + checkValueOrder(null); + + mHighestYCache = mLowestYCache = Double.NaN; + + // update graphview + for (WeakReference gv : mGraphViews) { + if (gv != null && gv.get() != null) { + gv.get().onDataChanged(true, false); + } + } + } + + /** + * stores the reference of the used graph + * + * @param graphView graphview + */ + @Override + public void onGraphViewAttached(GraphView graphView) { + mGraphViews.add(new WeakReference<>(graphView)); + } + + /** + * + * @param dataPoint values the values must be in the correct order! + * x-value has to be ASC. First the lowest x value and at least the highest x value. + * @param scrollToEnd true => graphview will scroll to the end (maxX) + * @param maxDataPoints if max data count is reached, the oldest data + * value will be lost to avoid memory leaks + * @param silent set true to avoid rerender the graph + */ + public void appendData(E dataPoint, boolean scrollToEnd, int maxDataPoints, boolean silent) { + checkValueOrder(dataPoint); + + if (!mData.isEmpty() && dataPoint.getX() < mData.get(mData.size()-1).getX()) { + throw new IllegalArgumentException("new x-value must be greater then the last value. x-values has to be ordered in ASC."); + } + synchronized (mData) { + int curDataCount = mData.size(); + if (curDataCount < maxDataPoints) { + // enough space + mData.add(dataPoint); + } else { + // we have to trim one data + mData.remove(0); + mData.add(dataPoint); + } + + // update lowest/highest cache + double dataPointY = dataPoint.getY(); + if (!Double.isNaN(mHighestYCache)) { + if (dataPointY > mHighestYCache) { + mHighestYCache = dataPointY; + } + } + if (!Double.isNaN(mLowestYCache)) { + if (dataPointY < mLowestYCache) { + mLowestYCache = dataPointY; + } + } + + } + + if (!silent) { + // recalc the labels when it was the first data + boolean keepLabels = mData.size() != 1; + + // update linked graph views + // update graphview + for (WeakReference gv : mGraphViews) { + if (gv != null && gv.get() != null) { + if (scrollToEnd) { + gv.get().getViewport().scrollToEnd(); + } else { + gv.get().onDataChanged(keepLabels, scrollToEnd); + } + } + } + } + } + + /** + * + * @param dataPoint values the values must be in the correct order! + * x-value has to be ASC. First the lowest x value and at least the highest x value. + * @param scrollToEnd true => graphview will scroll to the end (maxX) + * @param maxDataPoints if max data count is reached, the oldest data + * value will be lost to avoid memory leaks + */ + public void appendData(E dataPoint, boolean scrollToEnd, int maxDataPoints) { + appendData(dataPoint, scrollToEnd, maxDataPoints, false); + } + + /** + * @return whether there are data points + */ + @Override + public boolean isEmpty() { + return mData.isEmpty(); + } + + /** + * checks that the data is in the correct order + * + * @param onlyLast if not null, it will only check that this + * datapoint is after the last point. + */ + protected void checkValueOrder(DataPointInterface onlyLast) { + if (mData.size()>1) { + if (onlyLast != null) { + // only check last + if (onlyLast.getX() < mData.get(mData.size()-1).getX()) { + throw new IllegalArgumentException("new x-value must be greater then the last value. x-values has to be ordered in ASC."); + } + } else { + double lx = mData.get(0).getX(); + + for (int i = 1; i < mData.size(); i++) { + if (mData.get(i).getX() != Double.NaN) { + if (lx > mData.get(i).getX()) { + throw new IllegalArgumentException("The order of the values is not correct. X-Values have to be ordered ASC. First the lowest x value and at least the highest x value."); + } + lx = mData.get(i).getX(); + } + } + } + } + } + + public abstract void drawSelection(GraphView mGraphView, Canvas canvas, boolean b, DataPointInterface value); + + public void clearCursorModeCache() { + mIsCursorModeCache = null; + } + + @Override + public void clearReference(GraphView graphView) { + // find and remove + for (WeakReference view : mGraphViews) { + if (view != null && view.get() != null && view.get() == graphView) { + mGraphViews.remove(view); + break; + } + } + } +} diff --git a/src/main/java/com/jjoe64/graphview/series/DataPoint.java b/src/main/java/com/jjoe64/graphview/series/DataPoint.java new file mode 100644 index 000000000..097a29c02 --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/series/DataPoint.java @@ -0,0 +1,60 @@ +/** + * GraphView + * Copyright 2016 Jonas Gehring + * + * 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.jjoe64.graphview.series; + +import android.provider.ContactsContract; + +import java.io.Serializable; +import java.util.Date; + +/** + * default data point implementation. + * This stores the x and y values. + * + * @author jjoe64 + */ +public class DataPoint implements DataPointInterface, Serializable { + private static final long serialVersionUID=1428263322645L; + + private double x; + private double y; + + public DataPoint(double x, double y) { + this.x=x; + this.y=y; + } + + public DataPoint(Date x, double y) { + this.x = x.getTime(); + this.y = y; + } + + @Override + public double getX() { + return x; + } + + @Override + public double getY() { + return y; + } + + @Override + public String toString() { + return "["+x+"/"+y+"]"; + } +} diff --git a/src/main/java/com/jjoe64/graphview/series/DataPointInterface.java b/src/main/java/com/jjoe64/graphview/series/DataPointInterface.java new file mode 100644 index 000000000..5d641f7d0 --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/series/DataPointInterface.java @@ -0,0 +1,38 @@ +/** + * GraphView + * Copyright 2016 Jonas Gehring + * + * 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.jjoe64.graphview.series; + +/** + * interface of data points. Implement this in order + * to use your class in {@link com.jjoe64.graphview.series.Series}. + * + * You can also use the default implementation {@link com.jjoe64.graphview.series.DataPoint} so + * you do not have to implement it for yourself. + * + * @author jjoe64 + */ +public interface DataPointInterface { + /** + * @return the x value + */ + public double getX(); + + /** + * @return the y value + */ + public double getY(); +} diff --git a/src/main/java/com/jjoe64/graphview/series/LineGraphSeries.java b/src/main/java/com/jjoe64/graphview/series/LineGraphSeries.java new file mode 100644 index 000000000..3d56125dd --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/series/LineGraphSeries.java @@ -0,0 +1,714 @@ +/** + * GraphView + * Copyright 2016 Jonas Gehring + *

    + * 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.jjoe64.graphview.series; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import androidx.core.view.ViewCompat; +import android.view.animation.AccelerateInterpolator; + +import com.jjoe64.graphview.GraphView; + +import java.util.Iterator; + +/** + * Series to plot the data as line. + * The line can be styled with many options. + * + * @author jjoe64 + */ +public class LineGraphSeries extends BaseSeries { + private static final long ANIMATION_DURATION = 333; + + /** + * wrapped styles regarding the line + */ + private final class Styles { + /** + * the thickness of the line. + * This option will be ignored if you are + * using a custom paint via {@link #setCustomPaint(android.graphics.Paint)} + */ + private int thickness = 5; + + /** + * flag whether the area under the line to the bottom + * of the viewport will be filled with a + * specific background color. + * + * @see #backgroundColor + */ + private boolean drawBackground = false; + + /** + * flag whether the data points are highlighted as + * a visible point. + * + * @see #dataPointsRadius + */ + private boolean drawDataPoints = false; + + /** + * the radius for the data points. + * + * @see #drawDataPoints + */ + private float dataPointsRadius = 10f; + + /** + * the background color for the filling under + * the line. + * + * @see #drawBackground + */ + private int backgroundColor = Color.argb(100, 172, 218, 255); + } + + /** + * wrapped styles + */ + private Styles mStyles; + + private Paint mSelectionPaint; + + /** + * internal paint object + */ + private Paint mPaint; + + /** + * paint for the background + */ + private Paint mPaintBackground; + + /** + * path for the background filling + */ + private Path mPathBackground; + + /** + * path to the line + */ + private Path mPath; + + /** + * custom paint that can be used. + * this will ignore the thickness and color styles. + */ + private Paint mCustomPaint; + + /** + * rendering is animated + */ + private boolean mAnimated; + + /** + * last animated value + */ + private double mLastAnimatedValue = Double.NaN; + + /** + * time of animation start + */ + private long mAnimationStart; + + /** + * animation interpolator + */ + private AccelerateInterpolator mAnimationInterpolator; + + /** + * number of animation frame to avoid lagging + */ + private int mAnimationStartFrameNo; + + /** + * flag whether the line should be drawn as a path + * or with single drawLine commands (more performance) + * By default we use drawLine because it has much more peformance. + * For some styling reasons it can make sense to draw as path. + */ + private boolean mDrawAsPath = false; + + /** + * creates a series without data + */ + public LineGraphSeries() { + init(); + } + + /** + * creates a series with data + * + * @param data data points + * important: array has to be sorted from lowest x-value to the highest + */ + public LineGraphSeries(E[] data) { + super(data); + init(); + } + + /** + * do the initialization + * creates internal objects + */ + protected void init() { + mStyles = new Styles(); + mPaint = new Paint(); + mPaint.setStrokeCap(Paint.Cap.ROUND); + mPaint.setStyle(Paint.Style.STROKE); + mPaintBackground = new Paint(); + + mSelectionPaint = new Paint(); + mSelectionPaint.setColor(Color.argb(80, 0, 0, 0)); + mSelectionPaint.setStyle(Paint.Style.FILL); + + mPathBackground = new Path(); + mPath = new Path(); + + mAnimationInterpolator = new AccelerateInterpolator(2f); + } + + /** + * plots the series + * draws the line and the background + * + * @param graphView graphview + * @param canvas canvas + * @param isSecondScale flag if it is the second scale + */ + @Override + public void draw(GraphView graphView, Canvas canvas, boolean isSecondScale) { + resetDataPoints(); + + // get data + double maxX = graphView.getViewport().getMaxX(false); + double minX = graphView.getViewport().getMinX(false); + + double maxY; + double minY; + if (isSecondScale) { + maxY = graphView.getSecondScale().getMaxY(false); + minY = graphView.getSecondScale().getMinY(false); + } else { + maxY = graphView.getViewport().getMaxY(false); + minY = graphView.getViewport().getMinY(false); + } + + Iterator values = getValues(minX, maxX); + + // draw background + double lastEndY = 0; + double lastEndX = 0; + + // draw data + mPaint.setStrokeWidth(mStyles.thickness); + mPaint.setColor(getColor()); + mPaintBackground.setColor(mStyles.backgroundColor); + + Paint paint; + if (mCustomPaint != null) { + paint = mCustomPaint; + } else { + paint = mPaint; + } + + mPath.reset(); + + if (mStyles.drawBackground) { + mPathBackground.reset(); + } + + double diffY = maxY - minY; + double diffX = maxX - minX; + + float graphHeight = graphView.getGraphContentHeight(); + float graphWidth = graphView.getGraphContentWidth(); + float graphLeft = graphView.getGraphContentLeft(); + float graphTop = graphView.getGraphContentTop(); + + lastEndY = 0; + lastEndX = 0; + + // needed to end the path for background + double lastUsedEndX = 0; + double lastUsedEndY = 0; + float firstX = -1; + float firstY = -1; + float lastRenderedX = Float.NaN; + int i = 0; + float lastAnimationReferenceX = graphLeft; + + boolean sameXSkip = false; + float minYOnSameX = 0f; + float maxYOnSameX = 0f; + + while (values.hasNext()) { + E value = values.next(); + + double valY = value.getY() - minY; + double ratY = valY / diffY; + double y = graphHeight * ratY; + + double valueX = value.getX(); + double valX = valueX - minX; + double ratX = valX / diffX; + double x = graphWidth * ratX; + + double orgX = x; + double orgY = y; + + if (i > 0) { + // overdraw + boolean isOverdrawY = false; + boolean isOverdrawEndPoint = false; + boolean skipDraw = false; + + if (x > graphWidth) { // end right + double b = ((graphWidth - lastEndX) * (y - lastEndY) / (x - lastEndX)); + y = lastEndY + b; + x = graphWidth; + isOverdrawEndPoint = true; + } + if (y < 0) { // end bottom + // skip when previous and this point is out of bound + if (lastEndY < 0) { + skipDraw = true; + } else { + double b = ((0 - lastEndY) * (x - lastEndX) / (y - lastEndY)); + x = lastEndX + b; + } + y = 0; + isOverdrawY = isOverdrawEndPoint = true; + } + if (y > graphHeight) { // end top + // skip when previous and this point is out of bound + if (lastEndY > graphHeight) { + skipDraw = true; + } else { + double b = ((graphHeight - lastEndY) * (x - lastEndX) / (y - lastEndY)); + x = lastEndX + b; + } + y = graphHeight; + isOverdrawY = isOverdrawEndPoint = true; + } + if (lastEndX < 0) { // start left + double b = ((0 - x) * (y - lastEndY) / (lastEndX - x)); + lastEndY = y - b; + lastEndX = 0; + } + + // we need to save the X before it will be corrected when overdraw y + float orgStartX = (float) lastEndX + (graphLeft + 1); + + if (lastEndY < 0) { // start bottom + if (!skipDraw) { + double b = ((0 - y) * (x - lastEndX) / (lastEndY - y)); + lastEndX = x - b; + } + lastEndY = 0; + isOverdrawY = true; + } + if (lastEndY > graphHeight) { // start top + // skip when previous and this point is out of bound + if (!skipDraw) { + double b = ((graphHeight - y) * (x - lastEndX) / (lastEndY - y)); + lastEndX = x - b; + } + lastEndY = graphHeight; + isOverdrawY = true; + } + + float startX = (float) lastEndX + (graphLeft + 1); + float startY = (float) (graphTop - lastEndY) + graphHeight; + float endX = (float) x + (graphLeft + 1); + float endY = (float) (graphTop - y) + graphHeight; + float startXAnimated = startX; + float endXAnimated = endX; + + if (endX < startX) { + // dont draw from right to left + skipDraw = true; + } + + // NaN can happen when previous and current value is out of y bounds + if (!skipDraw && !Float.isNaN(startY) && !Float.isNaN(endY)) { + // animation + if (mAnimated) { + if ((Double.isNaN(mLastAnimatedValue) || mLastAnimatedValue < valueX)) { + long currentTime = System.currentTimeMillis(); + if (mAnimationStart == 0) { + // start animation + mAnimationStart = currentTime; + mAnimationStartFrameNo = 0; + } else { + // anti-lag: wait a few frames + if (mAnimationStartFrameNo < 15) { + // second time + mAnimationStart = currentTime; + mAnimationStartFrameNo++; + } + } + float timeFactor = (float) (currentTime - mAnimationStart) / ANIMATION_DURATION; + float factor = mAnimationInterpolator.getInterpolation(timeFactor); + if (timeFactor <= 1.0) { + startXAnimated = (startX - lastAnimationReferenceX) * factor + lastAnimationReferenceX; + startXAnimated = Math.max(startXAnimated, lastAnimationReferenceX); + endXAnimated = (endX - lastAnimationReferenceX) * factor + lastAnimationReferenceX; + ViewCompat.postInvalidateOnAnimation(graphView); + } else { + // animation finished + mLastAnimatedValue = valueX; + } + } else { + lastAnimationReferenceX = endX; + } + } + + // draw data point + if (!isOverdrawEndPoint) { + if (mStyles.drawDataPoints) { + // draw first datapoint + Paint.Style prevStyle = paint.getStyle(); + paint.setStyle(Paint.Style.FILL); + canvas.drawCircle(endXAnimated, endY, mStyles.dataPointsRadius, paint); + paint.setStyle(prevStyle); + } + registerDataPoint(endX, endY, value); + } + + if (mDrawAsPath) { + mPath.moveTo(startXAnimated, startY); + } + // performance opt. + if (Float.isNaN(lastRenderedX) || Math.abs(endX - lastRenderedX) > .3f) { + if (mDrawAsPath) { + mPath.lineTo(endXAnimated, endY); + } else { + // draw vertical lines that were skipped + if (sameXSkip) { + sameXSkip = false; + renderLine(canvas, new float[]{lastRenderedX, minYOnSameX, lastRenderedX, maxYOnSameX}, paint); + } + renderLine(canvas, new float[]{startXAnimated, startY, endXAnimated, endY}, paint); + } + lastRenderedX = endX; + } else { + // rendering on same x position + // save min+max y position and draw it as line + if (sameXSkip) { + minYOnSameX = Math.min(minYOnSameX, endY); + maxYOnSameX = Math.max(maxYOnSameX, endY); + } else { + // first + sameXSkip = true; + minYOnSameX = Math.min(startY, endY); + maxYOnSameX = Math.max(startY, endY); + } + } + + } + + if (mStyles.drawBackground) { + if (isOverdrawY) { + // start draw original x + if (firstX == -1) { + firstX = orgStartX; + firstY = startY; + mPathBackground.moveTo(orgStartX, startY); + } + // from original start to new start + mPathBackground.lineTo(startXAnimated, startY); + } + if (firstX == -1) { + firstX = startXAnimated; + firstY = startY; + mPathBackground.moveTo(startXAnimated, startY); + } + mPathBackground.lineTo(startXAnimated, startY); + mPathBackground.lineTo(endXAnimated, endY); + } + + lastUsedEndX = endXAnimated; + lastUsedEndY = endY; + } else if (mStyles.drawDataPoints) { + //fix: last value not drawn as datapoint. Draw first point here, and then on every step the end values (above) + float first_X = (float) x + (graphLeft + 1); + float first_Y = (float) (graphTop - y) + graphHeight; + + if (first_X >= graphLeft && first_Y <= (graphTop + graphHeight)) { + if (mAnimated && (Double.isNaN(mLastAnimatedValue) || mLastAnimatedValue < valueX)) { + long currentTime = System.currentTimeMillis(); + if (mAnimationStart == 0) { + // start animation + mAnimationStart = currentTime; + } + float timeFactor = (float) (currentTime - mAnimationStart) / ANIMATION_DURATION; + float factor = mAnimationInterpolator.getInterpolation(timeFactor); + if (timeFactor <= 1.0) { + first_X = (first_X - lastAnimationReferenceX) * factor + lastAnimationReferenceX; + ViewCompat.postInvalidateOnAnimation(graphView); + } else { + // animation finished + mLastAnimatedValue = valueX; + } + } + + + Paint.Style prevStyle = paint.getStyle(); + paint.setStyle(Paint.Style.FILL); + canvas.drawCircle(first_X, first_Y, mStyles.dataPointsRadius, paint); + paint.setStyle(prevStyle); + registerDataPoint(first_X, first_Y, value); + } + } + lastEndY = orgY; + lastEndX = orgX; + i++; + } + + if (mDrawAsPath) { + // draw at the end + canvas.drawPath(mPath, paint); + } + + if (mStyles.drawBackground && firstX != -1) { + // end / close path + if (lastUsedEndY != graphHeight + graphTop) { + // dont draw line to same point, otherwise the path is completely broken + mPathBackground.lineTo((float) lastUsedEndX, graphHeight + graphTop); + } + mPathBackground.lineTo(firstX, graphHeight + graphTop); + if (firstY != graphHeight + graphTop) { + // dont draw line to same point, otherwise the path is completely broken + mPathBackground.lineTo(firstX, firstY); + } + //mPathBackground.close(); + canvas.drawPath(mPathBackground, mPaintBackground); + } + } + + /** + * just a wrapper to draw lines on canvas + * + * @param canvas + * @param pts + * @param paint + */ + private void renderLine(Canvas canvas, float[] pts, Paint paint) { + if (pts.length == 4 && pts[0] == pts[2] && pts[1] == pts[3]) { + // avoid zero length lines, to makes troubles on some devices + // see https://github.com/appsthatmatter/GraphView/issues/499 + return; + } + canvas.drawLines(pts, paint); + } + + /** + * the thickness of the line. + * This option will be ignored if you are + * using a custom paint via {@link #setCustomPaint(android.graphics.Paint)} + * + * @return the thickness of the line + */ + public int getThickness() { + return mStyles.thickness; + } + + /** + * the thickness of the line. + * This option will be ignored if you are + * using a custom paint via {@link #setCustomPaint(android.graphics.Paint)} + * + * @param thickness thickness of the line + */ + public void setThickness(int thickness) { + mStyles.thickness = thickness; + } + + /** + * flag whether the area under the line to the bottom + * of the viewport will be filled with a + * specific background color. + * + * @return whether the background will be drawn + * @see #getBackgroundColor() + */ + public boolean isDrawBackground() { + return mStyles.drawBackground; + } + + /** + * flag whether the area under the line to the bottom + * of the viewport will be filled with a + * specific background color. + * + * @param drawBackground whether the background will be drawn + * @see #setBackgroundColor(int) + */ + public void setDrawBackground(boolean drawBackground) { + mStyles.drawBackground = drawBackground; + } + + /** + * flag whether the data points are highlighted as + * a visible point. + * + * @return flag whether the data points are highlighted + * @see #setDataPointsRadius(float) + */ + public boolean isDrawDataPoints() { + return mStyles.drawDataPoints; + } + + /** + * flag whether the data points are highlighted as + * a visible point. + * + * @param drawDataPoints flag whether the data points are highlighted + * @see #setDataPointsRadius(float) + */ + public void setDrawDataPoints(boolean drawDataPoints) { + mStyles.drawDataPoints = drawDataPoints; + } + + /** + * @return the radius for the data points. + * @see #setDrawDataPoints(boolean) + */ + public float getDataPointsRadius() { + return mStyles.dataPointsRadius; + } + + /** + * @param dataPointsRadius the radius for the data points. + * @see #setDrawDataPoints(boolean) + */ + public void setDataPointsRadius(float dataPointsRadius) { + mStyles.dataPointsRadius = dataPointsRadius; + } + + /** + * @return the background color for the filling under + * the line. + * @see #setDrawBackground(boolean) + */ + public int getBackgroundColor() { + return mStyles.backgroundColor; + } + + /** + * @param backgroundColor the background color for the filling under + * the line. + * @see #setDrawBackground(boolean) + */ + public void setBackgroundColor(int backgroundColor) { + mStyles.backgroundColor = backgroundColor; + } + + /** + * custom paint that can be used. + * this will ignore the thickness and color styles. + * + * @param customPaint the custom paint to be used for rendering the line + */ + public void setCustomPaint(Paint customPaint) { + this.mCustomPaint = customPaint; + } + + /** + * @param animated activate the animated rendering + */ + public void setAnimated(boolean animated) { + this.mAnimated = animated; + } + + /** + * flag whether the line should be drawn as a path + * or with single drawLine commands (more performance) + * By default we use drawLine because it has much more peformance. + * For some styling reasons it can make sense to draw as path. + */ + public boolean isDrawAsPath() { + return mDrawAsPath; + } + + /** + * flag whether the line should be drawn as a path + * or with single drawLine commands (more performance) + * By default we use drawLine because it has much more peformance. + * For some styling reasons it can make sense to draw as path. + * + * @param mDrawAsPath true to draw as path + */ + public void setDrawAsPath(boolean mDrawAsPath) { + this.mDrawAsPath = mDrawAsPath; + } + + /** + * + * @param dataPoint values the values must be in the correct order! + * x-value has to be ASC. First the lowest x value and at least the highest x value. + * @param scrollToEnd true => graphview will scroll to the end (maxX) + * @param maxDataPoints if max data count is reached, the oldest data + * value will be lost to avoid memory leaks + * @param silent set true to avoid rerender the graph + */ + public void appendData(E dataPoint, boolean scrollToEnd, int maxDataPoints, boolean silent) { + if (!isAnimationActive()) { + mAnimationStart = 0; + } + super.appendData(dataPoint, scrollToEnd, maxDataPoints, silent); + } + + /** + * @return currently animation is active + */ + private boolean isAnimationActive() { + if (mAnimated) { + long curr = System.currentTimeMillis(); + return curr - mAnimationStart <= ANIMATION_DURATION; + } + return false; + } + + @Override + public void drawSelection(GraphView graphView, Canvas canvas, boolean b, DataPointInterface value) { + double spanX = graphView.getViewport().getMaxX(false) - graphView.getViewport().getMinX(false); + double spanXPixel = graphView.getGraphContentWidth(); + + double spanY = graphView.getViewport().getMaxY(false) - graphView.getViewport().getMinY(false); + double spanYPixel = graphView.getGraphContentHeight(); + + double pointX = (value.getX() - graphView.getViewport().getMinX(false)) * spanXPixel / spanX; + pointX += graphView.getGraphContentLeft(); + + double pointY = (value.getY() - graphView.getViewport().getMinY(false)) * spanYPixel / spanY; + pointY = graphView.getGraphContentTop() + spanYPixel - pointY; + + // border + canvas.drawCircle((float) pointX, (float) pointY, 30f, mSelectionPaint); + + // fill + Paint.Style prevStyle = mPaint.getStyle(); + mPaint.setStyle(Paint.Style.FILL); + canvas.drawCircle((float) pointX, (float) pointY, 23f, mPaint); + mPaint.setStyle(prevStyle); + } +} diff --git a/src/main/java/com/jjoe64/graphview/series/OnDataPointTapListener.java b/src/main/java/com/jjoe64/graphview/series/OnDataPointTapListener.java new file mode 100644 index 000000000..e846e329e --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/series/OnDataPointTapListener.java @@ -0,0 +1,35 @@ +/** + * GraphView + * Copyright 2016 Jonas Gehring + * + * 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.jjoe64.graphview.series; + +/** + * Listener for the tap event which will be + * triggered when the user touches on a datapoint. + * + * Use this in {@link com.jjoe64.graphview.series.BaseSeries#setOnDataPointTapListener(OnDataPointTapListener)} + * + * @author jjoe64 + */ +public interface OnDataPointTapListener { + /** + * gets called when the user touches on a datapoint. + * + * @param series the corresponding series + * @param dataPoint the data point that was tapped on + */ + void onTap(Series series, DataPointInterface dataPoint); +} diff --git a/src/main/java/com/jjoe64/graphview/series/PointsGraphSeries.java b/src/main/java/com/jjoe64/graphview/series/PointsGraphSeries.java new file mode 100644 index 000000000..2ec74a526 --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/series/PointsGraphSeries.java @@ -0,0 +1,318 @@ +/** + * GraphView + * Copyright 2016 Jonas Gehring + * + * 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.jjoe64.graphview.series; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Point; + +import com.jjoe64.graphview.GraphView; + +import java.util.Iterator; + +/** + * Series that plots the data as points. + * The points can be different shapes or a + * complete custom drawing. + * + * @author jjoe64 + */ +public class PointsGraphSeries extends BaseSeries { + /** + * interface to implement a custom + * drawing for the data points. + */ + public static interface CustomShape { + /** + * called when drawing a single data point. + * use the x and y coordinates to draw your + * drawing at this point. + * + * @param canvas canvas to draw on + * @param paint internal paint object. this has the correct color. + * But you can use your own paint. + * @param x x-coordinate the point has to be drawn to + * @param y y-coordinate the point has to be drawn to + * @param dataPoint the related data point + */ + void draw(Canvas canvas, Paint paint, float x, float y, DataPointInterface dataPoint); + } + + /** + * choose a predefined shape to draw for + * each data point. + * You can also draw a custom drawing via {@link com.jjoe64.graphview.series.PointsGraphSeries.CustomShape} + */ + public enum Shape { + /** + * draws a point / circle + */ + POINT, + + /** + * draws a triangle + */ + TRIANGLE, + + /** + * draws a rectangle + */ + RECTANGLE + } + + /** + * wrapped styles for this series + */ + private final class Styles { + /** + * this is used for the size of the shape that + * will be drawn. + * This is useless if you are using a custom shape. + */ + float size; + + /** + * the shape that will be drawn for each point. + */ + Shape shape; + } + + /** + * wrapped styles + */ + private Styles mStyles; + + /** + * internal paint object + */ + private Paint mPaint; + + /** + * handler to use a custom drawing + */ + private CustomShape mCustomShape; + + /** + * creates the series without data + */ + public PointsGraphSeries() { + init(); + } + + /** + * creates the series with data + * + * @param data datapoints + */ + public PointsGraphSeries(E[] data) { + super(data); + init(); + } + + /** + * inits the internal objects + * set the defaults + */ + protected void init() { + mStyles = new Styles(); + mStyles.size = 20f; + mPaint = new Paint(); + mPaint.setStrokeCap(Paint.Cap.ROUND); + setShape(Shape.POINT); + } + + /** + * plot the data to the viewport + * + * @param graphView graphview + * @param canvas canvas to draw on + * @param isSecondScale whether it is the second scale + */ + @Override + public void draw(GraphView graphView, Canvas canvas, boolean isSecondScale) { + resetDataPoints(); + + // get data + double maxX = graphView.getViewport().getMaxX(false); + double minX = graphView.getViewport().getMinX(false); + + double maxY; + double minY; + if (isSecondScale) { + maxY = graphView.getSecondScale().getMaxY(false); + minY = graphView.getSecondScale().getMinY(false); + } else { + maxY = graphView.getViewport().getMaxY(false); + minY = graphView.getViewport().getMinY(false); + } + + Iterator values = getValues(minX, maxX); + + // draw background + double lastEndY = 0; + double lastEndX = 0; + + // draw data + mPaint.setColor(getColor()); + + double diffY = maxY - minY; + double diffX = maxX - minX; + + float graphHeight = graphView.getGraphContentHeight(); + float graphWidth = graphView.getGraphContentWidth(); + float graphLeft = graphView.getGraphContentLeft(); + float graphTop = graphView.getGraphContentTop(); + + lastEndY = 0; + lastEndX = 0; + float firstX = 0; + int i=0; + while (values.hasNext()) { + E value = values.next(); + + double valY = value.getY() - minY; + double ratY = valY / diffY; + double y = graphHeight * ratY; + + double valX = value.getX() - minX; + double ratX = valX / diffX; + double x = graphWidth * ratX; + + double orgX = x; + double orgY = y; + + // overdraw + boolean overdraw = false; + if (x > graphWidth) { // end right + overdraw = true; + } + if (y < 0) { // end bottom + overdraw = true; + } + if (y > graphHeight) { // end top + overdraw = true; + } + /* Fix a bug that continue to show the DOT after Y axis */ + if(x < 0) { + overdraw = true; + } + + float endX = (float) x + (graphLeft + 1); + float endY = (float) (graphTop - y) + graphHeight; + registerDataPoint(endX, endY, value); + + // draw data point + if (!overdraw) { + if (mCustomShape != null) { + mCustomShape.draw(canvas, mPaint, endX, endY, value); + } else if (mStyles.shape == Shape.POINT) { + canvas.drawCircle(endX, endY, mStyles.size, mPaint); + } else if (mStyles.shape == Shape.RECTANGLE) { + canvas.drawRect(endX-mStyles.size, endY-mStyles.size, endX+mStyles.size, endY+mStyles.size, mPaint); + } else if (mStyles.shape == Shape.TRIANGLE) { + Point[] points = new Point[3]; + points[0] = new Point((int)endX, (int)(endY-getSize())); + points[1] = new Point((int)(endX+getSize()), (int)(endY+getSize()*0.67)); + points[2] = new Point((int)(endX-getSize()), (int)(endY+getSize()*0.67)); + drawArrows(points, canvas, mPaint); + } + } + + i++; + } + + } + + /** + * helper to draw triangle + * + * @param point array with 3 coordinates + * @param canvas canvas to draw on + * @param paint paint object + */ + private void drawArrows(Point[] point, Canvas canvas, Paint paint) { + float [] points = new float[8]; + points[0] = point[0].x; + points[1] = point[0].y; + points[2] = point[1].x; + points[3] = point[1].y; + points[4] = point[2].x; + points[5] = point[2].y; + points[6] = point[0].x; + points[7] = point[0].y; + + canvas.drawVertices(Canvas.VertexMode.TRIANGLES, 8, points, 0, null, 0, null, 0, null, 0, 0, paint); + Path path = new Path(); + path.moveTo(point[0].x , point[0].y); + path.lineTo(point[1].x,point[1].y); + path.lineTo(point[2].x,point[2].y); + canvas.drawPath(path,paint); + } + + /** + * This is used for the size of the shape that + * will be drawn. + * This is useless if you are using a custom shape. + * + * @return the size of the shape + */ + public float getSize() { + return mStyles.size; + } + + /** + * This is used for the size of the shape that + * will be drawn. + * This is useless if you are using a custom shape. + * + * @param radius the size of the shape + */ + public void setSize(float radius) { + mStyles.size = radius; + } + + /** + * @return the shape that will be drawn for each point + */ + public Shape getShape() { + return mStyles.shape; + } + + /** + * @param s the shape that will be drawn for each point + */ + public void setShape(Shape s) { + mStyles.shape = s; + } + + /** + * Use a custom handler to draw your own + * drawing for each data point. + * + * @param shape handler to use a custom drawing + */ + public void setCustomShape(CustomShape shape) { + mCustomShape = shape; + } + + @Override + public void drawSelection(GraphView mGraphView, Canvas canvas, boolean b, DataPointInterface value) { + // TODO + } +} diff --git a/src/main/java/com/jjoe64/graphview/series/Series.java b/src/main/java/com/jjoe64/graphview/series/Series.java new file mode 100644 index 000000000..95e82a7e1 --- /dev/null +++ b/src/main/java/com/jjoe64/graphview/series/Series.java @@ -0,0 +1,129 @@ +/** + * GraphView + * Copyright 2016 Jonas Gehring + * + * 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.jjoe64.graphview.series; + +import android.graphics.Canvas; + +import com.jjoe64.graphview.GraphView; + +import java.util.Iterator; + +/** + * Basis interface for series that can be plotted + * on the graph. + * You can implement this in order to create a completely + * custom series type. + * But it is recommended to extend {@link com.jjoe64.graphview.series.BaseSeries} or another + * implemented Series class to save time. + * Anyway this interface can make sense if you want to implement + * a custom data provider, because BaseSeries uses a internal Array to store + * the data. + * + * @author jjoe64 + */ +public interface Series { + /** + * @return the lowest x-value of the data + */ + public double getLowestValueX(); + + /** + * @return the highest x-value of the data + */ + public double getHighestValueX(); + + /** + * @return the lowest y-value of the data + */ + public double getLowestValueY(); + + /** + * @return the highest y-value of the data + */ + public double getHighestValueY(); + + /** + * get the values for a specific range. It is + * important that the data comes in the sorted order + * (from lowest to highest x-value). + * + * @param from the minimal x-value + * @param until the maximal x-value + * @return all datapoints between the from and until x-value + * including the from and until data points. + */ + public Iterator getValues(double from, double until); + + /** + * Plots the series to the viewport. + * You have to care about overdrawing. + * This method may be called 2 times: one for + * the default scale and one time for the + * second scale. + * + * @param graphView corresponding graphview + * @param canvas canvas to draw on + * @param isSecondScale true if the drawing is for the second scale + */ + public void draw(GraphView graphView, Canvas canvas, boolean isSecondScale); + + /** + * @return the title of the series. Used in the legend + */ + public String getTitle(); + + /** + * @return the color of the series. Used in the legend and should + * be used for the plotted points or lines. + */ + public int getColor(); + + /** + * set a listener for tap on a data point. + * + * @param l listener + */ + public void setOnDataPointTapListener(OnDataPointTapListener l); + + /** + * called by the tap detector in order to trigger + * the on tap on datapoint event. + * + * @param x pixel + * @param y pixel + */ + void onTap(float x, float y); + + /** + * called when the series was added to a graph + * + * @param graphView graphview + */ + void onGraphViewAttached(GraphView graphView); + + /** + * @return whether there are data points + */ + boolean isEmpty(); + + /** + * clear reference to view and activity + * + * @param graphView + */ + void clearReference(GraphView graphView); +} diff --git a/src/main/res/values/attr.xml b/src/main/res/values/attr.xml new file mode 100644 index 000000000..8b7383860 --- /dev/null +++ b/src/main/res/values/attr.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/zooming.gif b/zooming.gif new file mode 100644 index 000000000..2b3dc215c Binary files /dev/null and b/zooming.gif differ