GraphLib: An open source Android library for graphs

GraphLib: An open source Android library for graphs

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, unfortunately, realistic pertaining to the author of this article.)

weightforyear John I. Moore

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.

download
Get the source code for the open source Android graphing library introduced in this article. Created by John I. Moore.

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 interface Function) 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 class Point in package android.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. 

download
Get the compiled Java source code for GraphLib. Created by John I. Moore.

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.

emptygraph John I. Moore

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.

Example 1: Graphing y = x2

Let's start by adding a simple function to a graph. If your version of Android Studio is configured to use Java 8 or later, you can use lambda expressions as shown in Listing 6.

Listing 6. Using a lambda expression.


Graph graph = new Graph.Builder()
    .addFunction(x -> x*x)
    .build();

If you are using a version of Java prior to Java 8, you will need to create an instance of a class that implements the Function interface. I've done this in Listing 7 using an anonymous class.

Listing 7. Using an anonymous class


Function xSquared = new Function()
  {
    public double apply(double x)
      {
        return x*x;
      }
  };
Graph graph = new Graph.Builder()
    .addFunction(xSquared)
    .build();

Figure 3 shows how this application would look on an Android device.

firstgraph John I. Moore

Figure 3. Graph of y = x2

Note: The remaining examples will use only lambda expressions for mathematical functions.

Example 2: Adding color and setting world coordinates

For this example we'll make a few changes to the previous example. First, let's change the color of the graph to red. There are a couple of ways to do this. We could set the default color for functions by calling method setFunctionColor() before calling addFunction(). Another option is to call the overloaded version of addFunction(), which has two parameters, both the function to be graphed and the color to be used for that graph. This example demonstrates the latter approach.

For another change to the previous example, let's adjust the world coordinates (a.k.a. window) for the graph. Notice how much of the graph in Figure 3 is in the top half of the view. For this example we'll let the x-axis range from -5 to 5 and the y-axis range from -2 to 10. When we make this change, we also need to modify the "tick" marks and labels on the axes, using calls to methods setXTicks() and setYTicks(). Listing 8 shows the code to build this graph.

Listing 8. Adding color and setting world coordinates


Graph graph = new Graph.Builder()
    .addFunction(x -> x*x, Color.RED)
    .setWorldCoordinates(-5, 5, -2, 20)
    .setXTicks(new double[] {-4, -3, -2, -1, 1, 2, 3, 4})
    .setYTicks(new double[] {2, 4, 6, 8, 10, 12, 14, 16, 18})
    .build();

Figure 4 shows the resulting application running on an Android device.

firstgraphwithcolor John I. Moore

Figure 4. Adding color and setting world coordinates

Example 3: Graphing three functions

Observe that some of the builder methods shown in Listing 2 start with the set prefix, and some start with the add prefix. Those starting with set control access to a single attribute value, but those starting with add can be called multiple times to append to a list of similar attribute values. For example, we can call either of the overloaded versions of the addFunction() method to add more than one function to a graph.

Listing 9 shows how to add three functions, each with different colors.

Listing 9. Graphing two functions


Graph graph = new Graph.Builder()
    .addFunction(x -> sin(x), Color.RED)
    .addFunction(x -> 0.1*x*x*x, Color.BLUE)
    .addFunction(x -> 1/x, Color.GREEN)
    .setWorldCoordinates(-2*Math.PI, 2*Math.PI, -5, 5)
    .setXTicks(new double[] {-3, -1, 1, 3})
    .setYTicks(new double[] {-3, -1, 1, 3})
    .build();

Figure 5 shows the application running on an Android device.

secondgraph John I. Moore

Figure 5. Graphing three functions

Example 4: Straight line plus data points

This example illustrates how to add data points to a graph. When a set of points visually appear to be in a pattern that is "almost" a straight line, it is possible use a technique known as linear regression to determine the line that best fits the data points. A detailed discussion of linear regression is beyond the scope of this article, but I have used that technique (with some numerical rounding) to find the formula for the line used in this example.

Listing 10 shows how to add both a function (in this case a straight line) and a set of four data points to a graph

Listing 10. Straight line plus data points


Graph graph = new Graph.Builder()
    .addFunction(x -> x*x -5)
    .addPoints(new Point[] { new Point(-6, -6), new Point(-2, 3),
                             new Point(2, 6),   new Point(5, 7)},
                             Color.RED)
    .build();

Figure 6 shows the application running on an Android device.

thirdgraph John I. Moore

Figure 6. Straight line plus data points

Example 5: Line graph

This example illustrates how to add a line graph. A line graph is defined by a set of points. Defining a line graph to be drawn on the screen is similar to defining a set data points to be plotted on the screen. The difference is that when defining a line graph, we need to call method addLineGraph() instead of calling addPoints(). Listing 11 shows how to add a line graph.

Listing 11. Line graph


Point[] points =
  {
    new Point(-10, 3), new Point(-8, 4),  new Point(5, 2),
    new Point(0, 0),   new Point(2, -6),  new Point(3,3),
    new Point(7,5),    new Point(9, 9),   new Point(12, 6)
  };
Graph graph = new Graph.Builder()
    .addLineGraph(points, Color.RED)
    .build();

Figure 7 shows the application running on an Android device.

fourthgraph John I. Moore

Figure 7. Line graph on an Android device

Example 6: Tracking weight for one month

For this example let's assume that we have a weight-tracking application that shows weights measured for several days throughout a given month. Similar to the example at the beginning of this article (and depicted in Figure 1), this example combines a horizontal goal weight line and a line graph showing actual weight measurements. Whereas the example shown in Figure 1 used string labels on the horizontal axis, this one will use numbers representing the days of the month when weights were taken.

Listing 12 shows the code to add both a horizontal green line for the goal weight and a line graph for the measured weight.

Listing 12. Tracking weight for one month


Point[] points =
  {
    new Point(1, 178),  new Point(4, 179),  new Point(7, 179),
    new Point(10, 181), new Point(13, 180), new Point(16, 182),
    new Point(19, 182), new Point(22, 184), new Point(25, 183),
    new Point(28, 185), new Point(31, 185)
  };
Graph graph = new Graph.Builder()
    .setWorldCoordinates(-5, 33, 165, 191)
    .setAxes(0, 167)
    .setXTicks(new double[] {5, 10, 15, 20, 25, 30})
    .setYTicks(new double[] {170, 175, 180, 185, 190})
    .addFunction(x -> 170, Color.GREEN)
    .addLineGraph(points, Color.RED)
    .build();

Figure 8 shows the application running on an Android device.

weightformonth John I. Moore

Figure 8. Tracking weight for one month

Example 7: Tracking weight for a year

We now come full circle back to that motivating example from Figure 1, showing weight measurements for an entire year. Similar to Example 6 above, this application combines a horizontal goal weight line together with a line graph showing actual weight measurements. But in this case we've added nonnumeric (string) labels for the x-axis.

Note, too, that all previous examples have used methods setXTicks() and setYTicks() to specify the placement of numeric labels for the axes. In this example, we use setXLabels() for the month abbreviations on the x-axis, but still use setYTicks() to show the numerical values for weights on the y-axis. Also note that I've used only the first letter of each month, accounting for space limitations on most Android phone screens.

Listing 13 shows the source code to create the graph.

Listing 13. Tracking weight for a year


Point[] points =
  {
    new Point(1, 178),  new Point(2, 179),  new Point(3, 179),
    new Point(4, 181),  new Point(5, 180),  new Point(6, 182),
    new Point(7, 182),  new Point(8, 184),  new Point(9, 183),
    new Point(10, 185), new Point(11, 185), new Point(12, 186)
  };
Label[] xLabels =
  {
    new Label(1, "J"),  new Label(2, "F"),  new Label(3, "M"),
    new Label(4, "A"),  new Label(5, "M"),  new Label(6, "J"),
    new Label(7, "J"),  new Label(8, "A"),  new Label(9, "S"),
    new Label(10, "O"), new Label(11, "N"), new Label(12, "D")
  };
Graph graph = new Graph.Builder()
    .setWorldCoordinates(-2, 13, 165, 191)
    .setAxes(0, 167)
    .setXLabels(xLabels)
    .setYTicks(new double[] {170, 175, 180, 185, 190})
    .addFunction(x -> 170, Color.GREEN)
    .addLineGraph(points, Color.RED)
    .build();

For convenience, Figure 9 simply repeats Figure 1, showing the application running on an Android device.

weightforyear John I. Moore

Figure 9. Tracking weight for the year (revisited)

Conclusion

Many Android applications can make effective use of graphs and data plots to help illustrate relationships, depict data trends, and track progress versus goals. I created the open source library GraphLib in order to support this type of functionality. As you've seen, GraphLib is an easy-to-use library for graphing functions, plotting data points, and drawing line graphs. This library is freely available, and you can get the source code for it here.

IDG Insider

PREVIOUS ARTICLE

«Carbonite review: Cleverly integrated online backup

NEXT ARTICLE

With the new Amazon Fire TV, voice becomes the killer feature»
author_image
IDG Connect

IDG Connect tackles the tech stories that matter to you

Add Your Comment

Most Recent Comments

Our Case Studies

IDG Connect delivers full creative solutions to meet all your demand generatlon needs. These cover the full scope of options, from customized content and lead delivery through to fully integrated campaigns.

images

Our Marketing Research

Our in-house analyst and editorial team create a range of insights for the global marketing community. These look at IT buying preferences, the latest soclal media trends and other zeitgeist topics.

images

Poll

Should companies have Bitcoins on hand in preparation for a Ransomware attack?