Package com.stimware.graphworkbench.spi

Service Provider Interface - This provides everything you need to know about creating your own algorithms.

See: Description

Package com.stimware.graphworkbench.spi Description

Service Provider Interface - This provides everything you need to know about creating your own algorithms.

Introduction

If you've got this far, it probably means that you want to create your own graphing algorithms using the Graph Workbench application. Hopefully this series of tutorials will explain it well enough to get started.

The first thing you need to do is get your head around the idea that the Graph Workbench application needs to plot a certain color for every coordinate on the graph that maps to a screen pixel. You only need to provide some code that tells it what color to use for that pixel. This is done by creating a class that implements the Algorithm interface, which has a single calculate method.

That's pretty much all there is to it. You don't need to worry about the process of iterating over pixels, calculating graph coordinates or gathering input parameters.

The Basics

Let's start with a simple example, and what can be simpler than drawing a square? Using basic 2D algorithms you would draw as series of lines connecting four points and possibly fill the interior.

But that's not how this works. Remember, the Graph Workbench will call your algorithm once for each pixel, requiring it to return with a color for that pixel. The approach required therefore is to determine whether or not the coordinate is inside or outside the square. The mathematics behind this is trivial - do the x and y coordinate fall within the bounds defined by each corner?

import com.stimware.graphworkbench.spi.Algorithm;
import java.awt.Color;

public class Tutorial_1_1 implements Algorithm {

    public Color calculate(double x, double y) {

        // Is this point within the square?
        if (x > -0.5 && x < 0.5) {
            if (y > -0.5 && y < 0.5) {
                return Color.WHITE;
            }
        }

        return Color.BLACK;
    }
}

You need to do a couple of things to get this code running in the Graph Workbench. Let's break it down into some easy steps:

  1. Compile you source code - This will require the GraphWorkBenchSPI.jar file to be in the classpath.
  2. Package the resulting classes - They need to go into a JAR file in order to be loaded by the Graph Workbench's Plugin Registry. You don't need to create any manifest or config files for this process.
  3. Load the plugin - Within Graph Workbench, open the File menu and select New Graph. This will first open the Plugin Registry to allow you to select an Algorithm plugin. Below the list of Algorithms is a button labeled Load Plugin. Click this button and browse for your JAR file.

Once this is done, your plugin package and all of its Algorithm classes will be listed in the Registry. They will also be available the next time you start the application.

Parameters

The result is a nice white square on a black background, but the result isn't particularly interesting. It would be nice to let the end user provide some input to make our square a little less mundane.

This is where parameters are required. You can specify any number and almost any type of variable to be input by the user and passed to the algorithm, which can then be used by the processing logic. Anything from colors, to the size of the shapes or the level of depth for more complex shapes can be requested from the user. All that is required to do this is to create a setter method in the algorithm for each parameter, which can be one of a large range of types. Here is an example:

import com.stimware.graphworkbench.spi.Algorithm;
import java.awt.Color;

public class Tutorial_1_2 implements Algorithm {

    Color color;

    public void setColor(Color color) {
        this.color = color;
    }

    public Color calculate(double x, double y) {

        // Is this point within the square?
        if (x > -0.5 && x < 0.5) {
            if (y > -0.5 && y < 0.5) {
                return color;
            }
        }

        return Color.BLACK;
    }
}

The user will now have the option of selecting a color. Don't worry about what sort of GUI component to use for inputting this color, the application will figure out the best way to present this to the user. It will then call your setter method with the color selected. In the above example the square will take on whatever color was selected.

You aren't limited to colors either. As mentioned before, almost any type of parameter can be specified and will be presenting using an appropriate input field:

Other types may be introduced in the future. If you specify a parameter that doesn't fit into one of the above categories, a simple text field will be used. The application will then try to construct an instance of a parameter variable using the string from the text field.

Guidelines

Now before we go any further, this would be a good time to lay down some ground rules that should be followed when writing your algorithms.

Annotations

Apart from writing more complex algorithms, one thing you may want to do is customize the way your algorithm appears in the registry, and how its parameters appear in the parameter window. This can be done by using a number of annotations in your source code. An example illustrates this best.

import com.stimware.graphworkbench.spi.Algorithm;
import com.stimware.graphworkbench.spi.Description;
import com.stimware.graphworkbench.spi.Parameter;
import java.awt.Color;

@Description(name="Tutorial 1-3")
public class Tutorial_1_3 implements Algorithm {

    Color color;

    @Parameter(defaultValue="red", label="Square Color")
    public void setColor(Color color) {
        this.color = color;
    }

    public Color calculate(double x, double y) {

        // Is this point within the square?
        if (x > -0.5 && x < 0.5) {
            if (y > -0.5 && y < 0.5) {
                return color;
            }
        }

        return Color.BLACK;
    }
}

This is no different to the previous example, except for the following cosmetic changes:

These annotations are purely optional but make using your algorithm a more pleasant experience.

Bounds

You are most certainly aware that it is possible to zoom in on areas of the graph and render specific portions. It is also possible to specify a region to render by default, rather than the square bound by the coordinates (-1,-1) and (1,1). This requires the DefaultBounds annotation.

import com.stimware.graphworkbench.spi.Algorithm;
import com.stimware.graphworkbench.spi.DefaultBounds;
import com.stimware.graphworkbench.spi.Description;
import com.stimware.graphworkbench.spi.Parameter;
import java.awt.Color;

@Description(name="Tutorial 1-4")
@DefaultBounds(top=2, right=2, bottom=-2, left=-2)
public class Tutorial_1_4 implements Algorithm {

    Color color;

    @Parameter(defaultValue="red", label="Square Color")
    public void setColor(Color color) {
        this.color = color;
    }

    public Color calculate(double x, double y) {

        // Is this point within the square?
        if (x > -0.5 && x < 0.5) {
            if (y > -0.5 && y < 0.5) {
                return color;
            }
        }

        return Color.BLACK;
    }
}

This one is actually zoomed out a bit. Sometimes your algorithm only displays interesting detail at certain graph coordinate, so you can either scale (or normalize if you will) the algorithm or get it to render just the interesting bits by default. The user can still of course enter their own coordinates of interest.

Gradient Color Model

All of this looks pretty darned boring with just flat color. One alternative to the approach of returning a single color under certain conditions is to calculate the color on the fly. This is not hard to do, but it would be nice to give the user some input into what sort of colors are used. This is where the ColorModel comes in to play, allowing users to specify some simple or complex gradients. Just created a parameter with the ColorModel type and the Graph Workbench will do all the hard work of capturing this complexity.

import com.stimware.graphworkbench.spi.Algorithm;
import com.stimware.graphworkbench.spi.ColorModel;
import com.stimware.graphworkbench.spi.Description;
import com.stimware.graphworkbench.spi.Parameter;
import java.awt.Color;

@Description(name="Tutorial 1-5")
public class Tutorial_1_5 implements Algorithm {

    ColorModel color;

    @Parameter(defaultValue="(length=1,start=yellow,end=red)", label="Square Color")
    public void setColor(ColorModel color) {
        this.color = color;
    }

    public void init() {
        color.setPeriod(10);
    }

    public Color calculate(double x, double y) {

        // Is this point within the square?
        if (x > -0.5 && x < 0.5) {
            if (y > -0.5 && y < 0.5) {
                double offset = x + 0.5;
                int index = (int)(offset * 10);
                return color.getColor(index);
            }
        }

        return Color.BLACK;
    }
}

The result of this algorithm is the same square but with a more visually pleasing gradient color starting from yellow on the left fading through to red on the right. The user can make any adjustments they like to this.

The important thing to know about a ColorModel is that they act as a function, giving you a color for any integer value passed in to them. Think of it as a series (or array) of colors of a set length, you just need to specify the index of the required color. This series is periodic, so will repeat after the highest index. For this reason you need to specify a period for the ColorModel.

Initialization

We have also sneakily introduced a new concept in this tutorial - initialization. You have the option of providing an init method which will be called once after the parameters have been passed and before the first call to your calculate method. This is the best place to set the period for any ColorModel parameters, which may also be defined as a parameter.

You may also want to perform any set up calculations here that may assist in your per-coordinate calculations later on. Feel free to set the values of any instance variables at this point.

Where to now?

Excellent question! The sorts of calculations your algorithms perform can be as complex as you like, in fact I originally designed the Graph Workbench as a test bed for various Fractal algorithms. Many of these such as the well known Julia and Mandelbrot sets require a great number of non-linear calculations that must be performed repeatedly to get a result. When all you need to do is write the code for each coordinate, the actual algorithm is deceptively simple and not much more involved than the examples shown above.

I encourage you to scrounge around the net for some details on fractal algorithms, and see if you can implement them using the Graph Workbench SPI. If you get stuck there are some already packaged with the application to give you an idea of what the end result may look like.