.. _canvas-layout:

Canvas Layout

In Toyplot, axes (including :ref:cartesian-axes, :ref:table-axes, and others) are used to map data values into canvas coordinates. The axes range (the area on the canvas that they occupy) is specified when they are created. By default, axes are sized to fill the entire canvas:


In [20]:
import numpy
y = numpy.linspace(0, 1, 20) ** 2

In [21]:
import toyplot
toyplot.plot(y, width=300);


051015200.00.51.0

If you need greater control over the positioning of the axes within the canvas, or want to add multiple axes to one canvas, it's necessary to create the canvas and axes explicitly, then use the axes to plot your data. For example, you can use the bounds argument to specify explicit (xmin, xmax, ymin, ymax) bounds for the axes using canvas coordinates (note that canvas coordinates always increase from top to bottom, unlike cartesian coordinates):


In [22]:
canvas = toyplot.Canvas(width=600, height=300)
axes1 = canvas.axes(bounds=(20, 280, 20, 280))
axes1.plot(y)
axes2 = canvas.axes(bounds=(320, 580, 20, 280))
axes2.plot(1 - y);


051015200.00.51.0051015200.00.51.0

You can also use negative values to specify values relative to the right and bottom sides of the canvas, instead of the (default) left and top sides, greatly simplifying the layout:


In [23]:
canvas = toyplot.Canvas(width=600, height=300)
axes1 = canvas.axes(bounds=(20, 280, 20, -20))
axes1.plot(y)
axes2 = canvas.axes(bounds=(-280, -20, 20, -20))
axes2.plot(1 - y);


051015200.00.51.0051015200.00.51.0

Furthermore, the bounds parameters can use any :ref:units, including "%" units, so you can use real-world units and relative dimensioning in any combination that makes sense:


In [24]:
canvas = toyplot.Canvas(width="20cm", height="2in")
axes1 = canvas.axes(bounds=("1cm", "5cm", "10%", "90%"))
axes1.plot(y)
axes2 = canvas.axes(bounds=("6cm", "-1cm", "10%", "90%"))
axes2.plot(1 - y);


051015200.00.51.0051015200.00.51.0

Of course, most of the time this level of control isn't necessary. Instead, the grid argument allows us to easily position each set of axes on a regular grid that covers the canvas. Note that you can control the axes position on the grid in a variety of ways:

  • (rows, columns, n)
    • fill cell $n$ (in left-to-right, top-to-bottom order) of an $M \times N$ grid.
  • (rows, columns, i, j)
    • fill cell $i,j$ of an $M \times N$ grid.
  • (rows, columns, i, rowspan, j, colspan)
    • fill cells $[i, i + rowspan), [j, j + colspan)$ of an $M \times N$ grid.

In [25]:
canvas = toyplot.Canvas(width=600, height=300)
axes1 = canvas.axes(grid=(1, 2, 0))
axes1.plot(y)
axes2 = canvas.axes(grid=(1, 2, 1))
axes2.plot(1 - y);


051015200.00.51.0051015200.00.51.0

You can also use the gutter argument to control the space between cells in the grid:


In [26]:
canvas = toyplot.Canvas(width=600, height=300)
axes1 = canvas.axes(grid=(1, 2, 0), gutter=15)
axes1.plot(y)
axes2 = canvas.axes(grid=(1, 2, 1), gutter=15)
axes2.plot(1 - y);


051015200.00.51.0051015200.00.51.0

Sometimes, particularly when embedding axes to produce a figure-within-a-figure, the corner argument can be used to position axes relative to one of eight "corner" positions within the canvas. The corner argument takes a (position, inset, width, height) tuple:


In [27]:
x = numpy.random.normal(size=100)
y = numpy.random.normal(size=100)

In [28]:
canvas = toyplot.Canvas(width="5in")
canvas.axes().plot(numpy.linspace(0, 1) ** 0.5)
canvas.axes(corner=("bottom-right", "1in", "1.5in", "1.5in")).scatterplot(x, y);


010203040500.00.51.0-2-1012-2-10123

Here are all the positions supported by the corner argument:


In [29]:
canvas = toyplot.Canvas(width="10cm")
for position in ["top-left", "top", "top-right", "right", "bottom-right", "bottom", "bottom-left", "left"]:
    canvas.axes(corner=(position, "1cm", "2cm", "2cm"), label=position)


-0.50.00.5-0.50.00.5top-left-0.50.00.5-0.50.00.5top-0.50.00.5-0.50.00.5top-right-0.50.00.5-0.50.00.5right-0.50.00.5-0.50.00.5bottom-right-0.50.00.5-0.50.00.5bottom-0.50.00.5-0.50.00.5bottom-left-0.50.00.5-0.50.00.5left