A guide to using Basis (updated for v0.6.0+)

This is a quick tour of Basis. Find the source for Basis on GitHub. Installing Basis is pretty simple; just grab it as a gem for your JRuby installation. Brief notes on the installation can be found here.

UPDATE: Starting from version 0.6.0, Basis allows you to specify axis labels. Additionally, you can specify arrays of points instead of plotting points one at a time. When you do this, you can also specify a corresponding legend string, which will show up in a legend guide. See below for more details.

UPDATE: Starting from version 0.5.9, you can turn grid lines on or off. Additionally, the matrix operations implementation has been ported to use the Matrix class in Ruby’s stdlib.

UPDATE: Starting from version 0.5.8, you can customise axis labels, draw arbitrary shapes/text/plot custom graphics at any point in your coordinate system. See below for more details.

UPDATE: With version 0.5.7, experimental support has been added for drawing objects which aren’t points. Interactions with such objects is currently not supported. Additional support for drawing markers/highlighting in custom bases is now in.

UPDATE: Starting from version 0.5.1, Basis has been ported to Ruby 1.9.2, because of the kd-tree library dependency. Currently, there are no plans of maintaining Basis compatibility with Ruby 1.8.x. As an aside, I personally recommend using RVM to manage the mess of Ruby/JRuby installations that you’re likely to have on your machine.

UPDATE: Basis has hit version 0.5.0 with experimental support for mouseover interactivity. More work is incoming, but the demo code below is up-to-date, for now. The code below should be the same as demo.rb on GitHub.

What is Basis?

Basis provides a set of classes for easily plotting and transforming arbitrary 2D coordinate systems by specifying their basis vectors in Ruby-Processing.
Here is some code which plots 200 random points.

The x_basis_vector and y_basis_vector variables correspond to the two basis vectors of our coordinate system. The Axis class encapsulates them together with the range of values along each dimension. The CoordinateSystem class encapsulates two Axis objects to manipulate our basis. The Screen class takes care of mapping the result to the display, including flipping the y-component.

The two numbers in the draw_axes method of Screen are the intervals for marking the ticks along the two basis vectors, respectively. The matrix [[4,0],[0,2]] in the constructor of CoordinateSystem gives the scaling/rotation of the data as specified in the basis vector space that you’ve defined; use it for stretching an axis that looks compressed or squeezing one which looks large.

The output looks like this:

You may choose to plot each point individually, as in the example above. However, you could have achieved the same thing by writing the following directly.

@screen.plot(points, :legend => 'Some Distribution', :track => true) do |original, mapped, s|
	s.in_basis do
		rect(original[:x], original[:y], 3, 3)

If you choose this route, you can specify a legend, as shown above. This will show up in a legend guide at a default position. You can customise the positioning of the legend box, while creating the Screen object, like so:

@screen = Screen.new(screen_transform, self, @basis, LegendBox.new(self, {:x => 1300, :y => 30}))

You have a few options when plotting a point. If you specify ‘:bar => true’, like the line below:

	@screen.plot(p, basis, :bar => true) {|p| rect(p[:x], p[:y], 5, 5)}

it will connect the point with the x-axis. If you omit `:bar => true` or don’t specify any options, Basis will plot the points without connecting them to the X-axis.
If you omit the block at the end of the call, Basis will plot the point using a circle. Use the block to customise how you wish to represent the point graphically.
You can toggle the joining of the points with lines by setting the join attribute of Screen to on (joins points)/off (no joining). The default is false.

Plotting markers inside/outside your custom basis

You now have more options for drawing objects on screen, either in the default basis (sans any transformation), or in the basis you’ve specified. If you’re using your specified basis, all shapes you draw (rectangles, ellipses, etc.) will be draw as they’d appear in the custom basis.

For example, in the code below, we are specifying that all our drawing (in this case, a filled rectangle) will be drawn using the basis specified while defining the Screen object.

@highlight_block = lambda do |original, mapped, s|
			s.in_basis do
			     rect(original[:x], original[:y], 3, 3)

Of course, in this case, the basis vectors are the standard ones, so you’ll not really notice anything. But let’s consider the follwing example.

In the above code, the basis vectors are (1,1) and (-1,1). The @highlight_block is the same as in the previous example. But if you run the above example, the highlighted rectangle will also appear rotated, as in the video below.

Contrast this with an alternate definition of @highlight_block, like so:

@highlight_block = lambda do |original, mapped, s|
				s.outside_basis do
				rect(mapped[:x], mapped[:y], 3, 3)

This will draw the highlighted rectangle unrotated. You can make use of the in_basis() and the outside_basis() methods to use one or the other. The default is to draw it in the default, untransformed, basis. For your convenience, the first two parameters passed to your block will be the original, and the mapped points, respectively.

Plotting objects which are not points

Some graphical representations of data might require more than simply plotting a single point. For example, a box plot is a concise way of summarising the distribution of a dataset; it is drawn not as a single point, but as a combination of boxes and ‘whiskers’. There is some support now in Basis to make this easier for you to do.
Consider the following code:

The first interesting thing in the code above is that the box hash does not have any values for the keys :x, :y. In such a situation, where Basis cannot identify a valid point, it defers the entirety of the plotting to the block that is passed to plot(). If no block is specified, it does nothing else. At this point, it is not possible to interact with objects which are not points, like in the above example.
The output looks like this:

The other interesting thing about the above code will become evident if we specify a different set of basis vectors like so:

@x_unit_vector = {:x => 1.0, :y => 0.15, :label => 'x-axis'}
@y_unit_vector = {:x => 0.2, :y => 1.0, :label => 'y-axis'}

Specifying the :label key is optional, Basis will generate its own legends if you don’t specify anything.
The output in this new host basis looks like so:

As you see, the box plot in this modified basis acquires the properties of skewness, rotation, etc. of the host coordinate system. This lets you get on with drawing the object without really worrying about the specific properties of the coordinate space you are plotting in.

The other variation you can use is the at() method of a Screen object. at() behaves like plot(), except that it never plots anything by itself, instead deferring full control to the block that you pass to it. Think of it as a simple way of accessing any point in your custom coordinate system, without having to perform any of the messy transformations yourself. So, for example, the following code fragment, puts some text at whichever point on screen corresponds to (some_x, some_y) in your custom basis.

@screen.at({:x => some_x, :y => some_y}) {|o,m,s| text(o[:y], m[:x] + 5, m[:y] + 14)}

Axis drawing options

Starting from version 0.6.0, you can specify the axis labels. There are two ways to do this. If you’re explicitly specifying your own basis vectors, you can specify the labels there, like so:

@x_unit_vector = {:x => 1.0, :y => 0.15, :label => 'x-axis'}
@y_unit_vector = {:x => 0.2, :y => 1.0, :label => 'y-axis'}

If you’re using the convenience method to initialise the standard CoordinateSystem, you can specify the labels like so:

@c = CoordinateSystem.standard(x_range, y_range, self, {:x => ‘Score’, :y => ‘Probability P(score)’})

In the code above, 😡 maps to ‘Score’ for the x-axis, and :y maps to ‘Probability P(score)’ for the y-axis.
Starting from version 0.5.8, you can customise labels, if you pass two blocks to the draw_axes() method, one for the x-axis, the other for the y-axis. For example, in the fragment below, we’re not outputting anything for the x-axis labels (returning an empty string), and converting the y-axis value to an integer.

@screen.draw_axes(10, 10, {:x => ->(p){''}, :y => ->(p){p.to_i}})

Grid lines are drawn by default. To turn them off, set the :gridlines key to false in the call to draw_axes(), like so:

@screen.draw_axes(10, 10, 😡 => ->(p){''}, :y => ->(p){p.to_i}, :gridlines => false)

The default CoordinateSystem

If you’re doing a simple plot, with standard basis vectors with no transformations, you can use the convenience method in CoordinateSystem to set up a default system.

@basis = CoordinateSystem.standard({:minimum => 0, :maximum => 200}, {:minimum => 0, :maximum => 300}, self)

If you want more control over your transformations and/or are using custom (possibly non-perpendicular) basis vectors, you’ll want to specify all of that explicitly. In the example below, we are setting up a CoordinateSystem with basis vectors (1,1) and (-1,1), with a nonuniform scaling transformation ((2,0),(0,3)).

@x_unit_vector = {:x => 1.0, :y => 1.0}
@y_unit_vector = {:x => -1.0, :y => 1.0}
x_range = ContinuousRange.new({:minimum => 0, :maximum => 200})
y_range = ContinuousRange.new({:minimum => 0, :maximum => 300})
@basis = CoordinateSystem.new(Axis.new(@x_unit_vector,x_range), Axis.new(@y_unit_vector,y_range), [[2,0],[0,3]], self)

Running the Code

Running the code is as simple as typing:

rp5 run demo.rb`

If you’re not using the Gems-in-a-Jar approach, you might have to use:

rp5 run --jruby demo.rb`

The reason for the `–jruby` switch is to force Ruby-Processing to use the installed version of JRuby, instead of it’s own jruby-complete.jar

Additionally, starting with version 0.5.1, you will have to specify the environment variable JRUBY_OPTS to force JRuby to use the 1.9 version of the interpreter, like so:

export JRUBY_OPTS=--1.9`

Renderer Compatibility

Basis has been tested with the Java2D and the Processing2D renderer. The OpenGL render mode needs some more work, because the cache behaves strangely in this mode.

Notes on Interactivity

Experimental support exists for mouseover interactivity without (too much) extra effort on your part. To allow interactions to happen, you must specify ‘:track => true’ while plotting a point, as in the line below:

@screen.plot(p, :track => true) do |original, mapped, s|
	s.in_basis do
		rect(original[:x], original[:y], 3, 3)

To actually enable interactivity, you must do a few things:

* ‘include’ the Interactive module in your sketch class, as in the demonstration code above, namely the line `Demo.send :include, Interactive`
* `@highlight_block` specifies what to plot when the data point is hovered upon.

Interactivity is a work in progress at the moment; some of the above steps may change or be removed.

As always, the code is available on GitHub.