Graphs and data plots are wonderful tools for illustrating relationships, depicting data trends, and tracking goals in your Android applications. I saw this for myself several years ago, when a former student of mine won first place in a student mobile app competition sponsored by the Charleston Defense Contractors Association. A key feature of the winning app, "Diabetes and Me," was the ability to graph daily sugar levels.
As another example, consider a weight-tracking application that plots progress against a goal weight. Figure 1 illustrates how such an application might look on an Android phone. The figure uses a red line-graph to show average monthly weights for the year 2017. It shows the goal weight as a green straight line near the bottom. (Although the data values shown in the line graph are hypothetical, they are realistic pertaining to the author of this article.)
Figure 1. Tracking weight for the year
In this article I'll use my open source library, GraphLib, to demonstrate the basics of graphing mathematical functions in Android. It's not the same graph library that my student used for his application. In fact, it's much simpler and easier to use.
Overview of GraphLib
GraphLib
consists of one interface and eight classes. Three of those classes are internal to the library and have only package access, so you will not need to understand them in order to use GraphLib. Two of the remaining classes have very simple functionality, and the remainder are not hard to pick up.
Below I will describe the GraphLib interface and each of its eight classes. Note that I used Java 8 features such as functional interfaces and lambda expressions to develop and test the library, but it's relatively straightforward to modify these features for earlier versions of Java.
GraphLib's functional interface
As shown in Listing 1, interface Function
has only one abstract method and is, therefore, a functional interface. Note that this interface is roughly equivalent to Java 8's DoubleUnaryOperator
, found in package java.util.function
. The difference is that Function
does not use any Java 8 features other than the annotation @FunctionalInterface
. Removing this annotation is the only change necessary to make the Function
interface compatible with earlier versions of Java.
Listing 1. interface Function
package com.softmoore.android.graphlib;
@FunctionalInterface
public interface Function
{
public double apply(double x);
}
GraphLib classes
Classes Point
and Label
are relatively simple: Point
encapsulates a pair of double values representing a point in the x,y-plane, and Label
encapsulates a double value and a string, where the double value represents a point on an axis and the string is used to label that point. The example in Figure 1 uses points to describe the line graph and labels for the axis at the bottom, showing one-letter abbreviations for the months. I'll provide more examples illustrating the use of these classes later in the article.
Classes GraphFunction
, GraphPoints
, and ScreenPoint
are not only very simple, they are also internal to the library and have only package access. You don't really need to understand these classes to use the library, so I'll describe them just briefly here:
GraphFunction
encapsulates a function (i.e., a class that implements interfaceFunction
) and a color used to draw that function.GraphPoints
encapsulates a list of points together with a color used to plot them. This class is used internally for both plotting points and drawing line graphs.ScreenPoint
encapsulates a pair of integer values representing pixel coordinates on the screen of an Android device. This class is similar to but simpler than the Android classPoint
in packageandroid.graphics
.
I've provided the source code for these classes in case you are interested in the details.
The three remaining classes in the GraphLib library are Graph
, Graph.Builder
, and GraphView
. It's important to understand the role that each of them plays in an Android application.
Class Graph
contains information about the colors, points, labels, graphs, etc., to be drawn, but is essentially independent of Android graphics details. While Graph
has a lot of fields, they all have default values, and therefore it makes sense to use the Builder pattern to create instances of this class. Class Graph
contains a nested static subclass named Builder
, which is used to create Graph
objects.
The two classes Graph
and Graph.Builder
go together, from a developer's perspective, and should be understood, essentially, as one. In truth, you only need to understand how to use the nested class Builder
to create a Graph
object. Developers don't really do anything directly with a Graph
object after it has been created other than pass it to a GraphView
object, which does the work of displaying everything on an Android device.
Listing 2 summarizes the methods available in class Graph.Builder
. Later examples will illustrate how to use the Builder pattern to create Graph
objects. For now, it's enough to note that, other than the default constructor (first line in Listing 2) and the build()
method (last line in Listing 2), all other methods return the Builder
object. This makes it possible to chain calls to builder methods.
Listing 2. Summary of methods in class Graph.Builder
public Builder()
public Builder addFunction(Function function, int graphColor)
public Builder addFunction(Function function)
public Builder addPoints(Point[] points, int pointColor)
public Builder addPoints(List<Point> points, int pointColor)
public Builder addPoints(Point[] points)
public Builder addPoints(List<Point> points)
public Builder addLineGraph(Point[] points, int lineGraphColor)
public Builder addLineGraph(List<Point> points, int lineGraphColor)
public Builder addLineGraph(Point[] points)
public Builder addLineGraph(List<Point> points)
public Builder setBackgroundColor(int bgColor)
public Builder setAxesColor(int axesColor)
public Builder setFunctionColor(int functColor)
public Builder setPointColor(int pointColor)
public Builder setWorldCoordinates(double xMin, double xMax, double yMin, double yMax)
public Builder setAxes(double axisX, double axisY)
public Builder setXTicks(double[] xTicks)
public Builder setXTicks(List<Double> xTicks)
public Builder setYTicks(double[] yTicks)
public Builder setYTicks(List<Double> yTicks)
public Builder setXLabels(Label[] xLabels)
public Builder setXLabels(List<Label> xLabels)
public Builder setYLabels(Label[] yLabels)
public Builder setYLabels(List<Label> yLabels)
public Graph build()
You'll note in Listing 2 that many of the methods are overloaded to accept either arrays of objects or lists of objects. I give preference to arrays over lists for examples in this article, simply because it is much easier to initialize arrays, but GraphLib
supports both. However, Java 9 will contain convenience factory methods for collections, thereby removing this small advantage for arrays. Were Java 9 in widespread use at the time of this article, I would have preferred lists over arrays in both GraphLib
and the later examples.
User interface classes in Android are called views, and class View
in package android.view
is the basic building block for user interface components. A view occupies a rectangular area on the screen, and is responsible for drawing and event handling. From an inheritance perspective, class View
is an ancestor class not only of user interface controls (buttons, text fields, etc.) but also of layouts, which are invisible view groups that are primarily responsible for arranging their child components.
Class GraphView
extends class View
and is responsible for displaying the information encapsulated in a Graph
on the screen of an Android device. Thus, class GraphView
is where all the drawing takes place.
Using GraphLib
There are two approaches to creating user interfaces for Android: a procedural approach (within the Java source code) or a declarative approach (in an XML file). Either one is valid, but the consensus is to use the declarative approach as much as possible. I've used a declarative approach for my examples.
There are five basic steps to using the GraphLib
library. Before you start, download the compiled Java source code for the GraphLib library.
Step 1. Make graphlib.jar available to your Android project
Create a new project using Android Studio and copy the JAR file graphlib.jar
to the libs
subdirectory of your project's app
directory. In Android Studio, switch the folder structure from Android to Project. Next, in the libs
folder (nested within the app
folder), right-click on the JAR file and click on Add as library. This last action will add the JAR file in the dependencies section of your app's build.gradle
file. See "How to add a jar in external libraries in Android Studio" if you need help with this step.
Step 2. Create an Android activity that will use GraphLib
In Android applications, an activity represents a single screen with a user interface. Activities are defined primarily in two files: an XML file that declares the UI layout and components, and a Java file that defines runtime functionality such as event handling. When a new project is created, Android Studio usually creates a default activity named MainActivity
. Use this activity or create a new one for your application.
Step 3. Add a GraphView to the layout for the activity
In the XML file for the activity's layout, you will declare a GraphView
object in much the same way that you declare a button or a text view, except that you need to provide the full package name for the GraphView
. Listing 3 shows an excerpt from a layout file that declares a GraphView
followed by a TextView
as part of a vertical linear layout. Following recommended practice, the actual values for the width and height of the GraphView
are defined in separate dimen
resource files, where different resource files provide values for different screen sizes/densities. (Note: I used 325 for both values in the examples below.)
Listing 3. Declaring a GraphView and a TextView in a layout XML file
<com.softmoore.android.graphlib.GraphView
android:id="@+id/graph_view"
android:layout_width="@dimen/graphView_width"
android:layout_height="@dimen/graphView_height"/>
<TextView
android:id="@+id/graph_view_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:textStyle="bold"/>
Step 4. Import the library classes into the activity
Listing 4 shows the list of import statements for an application if the library classes are imported individually. The list of imports can be abbreviated to a single line as import com.softmoore.android.graphlib.*
if desired. Personally, I prefer to see the expanded list as shown in Listing 4.
Listing 4. Import the library classes
import com.softmoore.android.graphlib.Function;
import com.softmoore.android.graphlib.Graph;
import com.softmoore.android.graphlib.GraphView;
import com.softmoore.android.graphlib.Label;
import com.softmoore.android.graphlib.Point;
Step 5. Create a Graph object and add it to the GraphView
Listing 5 shows the creation of a simple graph object--in this case a graph object that uses all of the default values. It essentially contains only a set of x- and y-axes, where the values on both axes range from 0 to 10. The listing also sets a title for the screen and text for the text view below the graph.
Listing 5. Create a Graph object and add it to the GraphView
Graph graph = new Graph.Builder()
.build();
GraphView graphView = findViewById(R.id.graph_view);
graphView.setGraph(graph);
setTitle("Empty Graph");
TextView textView = findViewById(R.id.graph_view_label);
textView.setText("Graph of Axes");
Figure 2 shows the result of running this application on an Android device.
Figure 2. An empty graph
Using GraphLib in Android applications
For the remainder of the article I'll focus on real-world uses of the GraphLib library in Android application development. I'll present seven examples with brief descriptions and source code excerpts. Note that the Java code listings for these examples are focused on using Graph.Builder
to create the appropriate Graph
object. Calls to findViewById()
, setGraph()
, setTitle()
, etc., would be similar to those shown in Listing 5 and are not included in the code listings.