Toolbox — Creating objects in Rayon

The Toolbox class is how users create new Rayon objects. This may seem unusual to long-time Python users who are used to creating and using their objects directly, as in the following example:

from rayon import foo
d = foo.Foo("bar.txt")
...

This approach carries with it some advantages (notably simplicity), but it can get unwieldy when the objects created come from many different modules:

# Note: the actual constructors for these objects are
# private; this example simpifies them, and won't run
# as it is written here.

from rayon import (
    foo_backend,
    borders,
    charts,
    data,
    plots)

def build_chart():
    d = data.Dataset.from_filename("sample_in.txt")
    c = charts.SquareChart()

    p = plots.ScatterPlot()
    p.set_data(x=d.column(0), y=d.column(1))
    c.add_plot(p)

    b = borders.HorizontalLabeledBorder(label="Title")
    c.add_top_border(b, height=.05)

    c.set_chart_background("white")
    return c

page = foo_backend.Page.from_filename(
    "sample_out.png", 800, 600)
page.write(build_chart())

Several more items have been appended to the import statement. When additional objects must be created (to add text labels, tick marks and so on) the statement can grow quite bulky. What’s more, if the developer wishes to move Rayon-object-creating code to another module, it is now necessary to parse out which import statements are needed and move them over, as well.

This code has an additional problem: it hard-codes the use of a specific rendering backend (foo_backend), even though it is only needed for the Page object. It would be nice if the code that renders the chart could do so without hardcoding the backend; this gives us flexibility to add or change backends in the future.

The Toolbox class eases this management burden by putting all object creation in one place:

# This example, unlike the previous one,
# actually works. :)

from rayon import toolbox

def build_chart(t):
    d = t.dataset_from_filename("sample_in.txt")
    c = t.new_chart("square")

    p = t.new_plot("scatter")
    p.set_data(x=d.column(0), y=d.column(1))
    c.add_plot(p)

    b1 = t.new_border("hlabel", label="Title")
    c.add_bottom_border(b, height=.05)

    c.set_chart_background("white")
    return c

t = toolbox.Toolbox.for_file()
page = t.new_page_from_filename(
    "sample_out.png", 800, 600)
page.write(build_chart(t))

The import statement is obviously much simpler. More importantly, if we choose to move build_chart to a different module, we don’t have to move any import statements over; we just have to pass it a Toolbox object when the function is invoked. As codebases grow, this kind of flexibility can be very useful.

Are we still dependent on a particular backend? Well, the Toolbox still needs to know whether we intend to render our visualization to a static file, or to a more dynamic surface like a GUI panel, which can change its dimensions and handle user interface actions. However, it no longer hardcodes a reference to the foo backend, meaning we can use other file-based backends in the future, should they become available. The build_chart function, meanwhile, doesn’t care at all what backend we are using, and can return charts that are usable when rendering to any backend.

Toolbox Nicknames

Many Rayon objects (e.g. markers, lines, borders) have several variants from which the user may choose. These variants can be specified via the Toolbox object using strings called nicknames.

To illustrate the use of nicknames, we will modify our example so it is a complete standalone script for generating scatterplots. The user can specify the input and output file. We will also frame the plot with lines on the bottom and left. The user will specify what style of line to use:

import sys
from rayon import toolbox

def build_chart(t, line_style):
    d = t.dataset_from_filename("sample_in.txt")
    c = t.new_chart("square")

    p = t.new_plot("scatter")
    p.set_data(x=d.column(0), y=d.column(1))
    c.add_plot(p)

    b1 = t.new_border("hlabel", label="Title")
    c.add_bottom_border(b, height=.05)

    l1 = t.new_line(line_style)
    btm_border = t.new_border("hline", line=l1)

    l2 = t.new_line(line_style)
    lft_border = t.new_border("vline", line=l2)

    c.set_chart_background("white")
    return c

# Warning: real, non-example scripts
# would check their input....
infile, outfile, line_style = sys.argv[1:]

t = toolbox.Toolbox.for_file()
page = t.new_page_from_filename(
    "sample_out.png", 800, 600)
page.write(build_chart(t))

The lines on the bottom and left of the visualization are specified directly from user input. This is a good deal easier than a hypothetical alternative based on static object constructors:

...

def build_chart(t, line_style):
    ...
    if line_style == 'solid':
        l1 = t.new_solid_line()
    elif line_style == 'dashed':
        l1 = t.new_dashed_line()
    elif ...

The nicknames used to create an object variant are listed in the Toolbox methods that create them. Throughout the Rayon User’s Guide and Rayon Reference Manual, object documentation lists the method (and nickname, where applicable) used to create them on the Toolbox object.