Tutorial¶
Introduction¶
The following tutorial will begin by describing the general paradigm of a CanD canvas, followed by a description of some of the main components which can be added to a canvas, and conclude with an annotated example.
The Canvas¶
A canvas in CanD, given by the Canvas
object, describes the physical
layout of the multi-paneled figure. Primarily, it contains a set of axes, which
are normal matplotlib axes objects. It also may contain other plot elements.
It can be thought of as a replacement for the matplotlib Figure object.
To start, we will want to import three core elements:
from cand import Canvas, Point, Vector
These are, in general, the three elements you probably want to import.
Canvas
can be used to create a new canvas, and Point
and
Vector
are used for measurements, which we will discuss soon.
To create a canvas c
which is 6 inches wide and 3 inches high, write:
c = Canvas(6, 3, "inches")
Note that we specified the size of the canvas in inches. There are many possible units we can use, including:
“in” (or “inch” or “inches”): Size in inches
“cm” (or “centimeter”, “centimetre”, “centimeters”, or “centimetres”): Size in centimeters.
“mm” (or “millimeter”, “millimetre”, “millimeters”, or “millimetres”): Position in millimeters.
“pt” (or “point” or “points”): The distance in points.
“px” (or “pixel” or “pixels”): The distance in pixels. (Note that the relationship between pixels and the other units depends on the DPI at which the figure is exported, more on this later in the tutorial.)
Once we have created the canvas, we can view it using any of the following three commands:
c.show() # Show a new window containing the canvas, or show inline if in Jupyter
c.save("output.png") # Save the canvas to the file "output.png"
c.save("output.pdf") # Save the canvas to the file "output.pdf"
This is all great, but there is nothing useful about seeing an empty canvas. Let’s look at how to position plots and other elements on the canvas.
Positions on the canvas¶
Once we have created a canvas of a size we are satisfied with, we can access
positions on the canvas using any units we would like. Each of the units above
can be used not only to specify the size of the canvas, but also to specify a
position on the canvas. Each of these assumes that the position (0,0) is at the
bottom left of the canvas. We specify a point by specifying the x position, y
position, and unit as arguments to “Point”, such as Point(3, 2, "cm")
to
access the point three centimeters from the left and two centimeters from the
bottom, or Point(0, 1, "in")
to access the point on the left side of the
figure, one inch from the bottom.
In addition to the units above, each canvas defines a set of units which may be used to position elements on that canvas. These include:
“absolute”: The default position, measured in matplotlib’s native coordinate system (currently inches). The origin is the bottom left corner of the figure. These coordinates are square, so one unit in the x direction is the same physical distance as one unit in the y direction. This is the default unit.
“-absolute”: Identical to “absolute”, except with the origin in the upper right corner.
“figure”: The fraction of the figure, where (0,0) is the bottom left corner and (1,1) is the upper right corner. These coordinates may not be square, i.e., if the figure itself is not square, then a displacement in the x direction may be a different visual distance than a displacement in the y direction.
“-figure”: Indentical to “figure”, except with the origin in the upper right corner.
“fontsize”: The default font size, e.g. if you have 8pt font, 2 units is 16pt. See below for how to change the font size. Note that this is computed by converting the size in points to the size on the figure. Thus, you cannot rely on this being exactly the same distance as a given font, because different fonts use different design choices. Nevertheless, it should be close.
“default” (or none): The default unit. This is set to “absolute” when the canvas is created, but this can be changed. For example,
Point(1, 2, "default")
andPoint(1, 2)
are equivalent.
We can also use different units for the x coordinate than we use for the y
coordinate by specifying the unit as a tuple of the two units. For example,
Point(0.5, 1.5, ("in", "cm"))
specifies the point
That’s a lot of units we can use to position elements on the canvas! We will see even more units introduced later in this tutorial.
Measuring distances¶
In addition to measuring positions on the canvas, we can measure distances.
Distances are measured using the Vector
object. Vectors are similar
to Points, except they do not have an origin. Thus, they measure distances and
sizes of elements. Vectors can use the same units as Points. For example,
Vector(1, 2, "in")
measures 1 inch in the x direction, and two inches in the y
direction.
Vectors and points are distinct because they have different functions. For instance, as we will see in the next section, we can add a Vector and a Vector, but we cannot add a Point and a Point in a meaningful way. Thus, we have two distinct objects: Points, specifying positions, and Vectors, specifying distances or sizes.
Units can also be created from existing units. Suppose we want to define a new
unit of “shifted_inches”, which is the same asthe unit “inches” but with an
origin half a unit up and half a unit to the right. For our canvas c
, we can
create this unit with:
c.add_unit("shifted_inches", Vector(1, 1, "inches"), origin=Point(.5, .5, "inches"))
Likewise, if we want a unit “in_cm” with inches on the x axis and centimeters on the y axis, but keeping the origin at the bottom left hand side of the canvas, we can do:
c.add_unit("in_cm", Vector(1, 1, ("inches", "cm")))
These new units can be used like existing units, e.g., Vector(1.5, 2.5,
"in_cm")
.
You can also move the origin. Here is a coordinate system where the center of the canvas is 0, the upper right corner is (1,1), and the lower left corner is (-1,-1).
c.add_unit(“center”, Vector(.5, .5, “figure”), origin=Point(.5, .5, “figure”))
Vector/Point arithmetic¶
It is possible to perform arithmetic on vectors, similar to the way we perform
vector operations in linear algebra. Two vectors can be added and subtracted,
and vectors can be multiplied and divided by a scalar. For example, Vector(0,
1, "cm") + Vector(1, 1, "cm")
is identical to Vector(1, 2, "cm")
, and
2*Vector(1, .5, "in")
is identical to Vector(2, 1, "in")
.
Likewise, it is possible to perform operations on vectors with different units.
For example, Vector(1, 0, "in") + Vector(1, 1, "cm")
is identical to
Vector(3.54, 1, "cm")
, which is approximately equal to Vector(1.3937, .3937,
"in")
. Likewise, Vector(1, 2, "cm") + Vector(.5, .5, "figure")
is a valid
vector, but the size of the vector depends on the size of the canvas, since
Vector(.5, .5, "figure")
is defined as half of the size of the canvas.
There are several operations we can perform between vectors:
“+”: Vector addition, e.g.,
Vector(0, 1) + Vector(2, 0) == Vector(2, 1)
“-”: Either the negative of a vector, e.g.,
-Vector(1, 2) == Vector(-1, -2)
, or vector subtraction, e.g.,Vector(2, 2) - Vector(.5, 1) == Vector(1.5, 1)
“*”: Multiply a vector by a scalar, e.g.,
2.5 * Vector(1, 2) == Vector(2.5, 5)
“/”: Divide a vector by a scalar, e.g.,
Vector(4, 2)/2 == Vector(2, 1)
There are also a few operations which are not standard linear algebra operations.
“>>”: Take the x value of the first vector and the y value of the second vector, discarding the rest, e.g.,
Vector(1, 2) >> Vector(3, 4) == Vector(1, 4)
“<<”: Take the y value of the first vector and the x value of the second vector, discarding the rest, e.g.,
Vector(1, 2) << Vector(3, 4) == Vector(3, 2)
“@”: Rotate the vector by a given number of degrees. Note that the rotation is always performed in square coordinates, so a 45 degree rotation will always appear to be a 45 degree rotation. In other words, the vector will first be converted to “absolute” units and then rotated. In square coordinates, this does not make a difference, so for example,
Vector(0, 1, "in") @ 45 == Vector(1/sqrt(2), 1/sqrt(2), "in")
We can also perform operations between Points and Vectors. For example, if we
want a point at the center of the figure but shifted up by 1 cm, we can do
Point(.5, .5, "figure") + Vector(0, 1, "cm")
.
Operations defined between Points and Vectors are:
“+”: Shift a point by an amount given by a vector, e.g.,
Point(1, 2) + Vector(2, 3) == Point(3, 5)
.“-”: Shift a point by the inverse of a vector, e.g.,
Point(5, 5) - Vector(1, 2) == Point(4, 3)
.
There are also operations defined between two Points:
“-”: Find the vector which connects the second point to the first point, e.g.,
Point(4, 3) - Point(1, 2) == Vector(3, 1)
.“>>”: Take the x value of the first point and the y value of the second point, discarding the rest, e.g.,
Point(1, 2) >> Point(3, 4) == Point(1, 4)
“<<”: Take the y value of the first vector and the x value of the second vector, discarding the rest, e.g.,
Point(1, 2) << Point(3, 4) == Point(3, 2)
“|”: Find the point in the middle of the two given points, e.g.,
Point(1, 1) | Point(2, 3) == Point(1.5, 2)
.
While all of these operators may seem daunting at first, as you gain experience using CanD, you will begin to find them more intuitive.
Creating an axis¶
Now that we have learned how to describe positions and distances on the canvas, let’s learn how to plot. In order to plot, we must first create an axis. Any given Canvas may have multiple axes. Axes are Matplotlib objects, and so to plot on them, you can use all standard Matplotlib commands. Note that you will need to use the so-called “object-oriented API” in Matplotlib. If you are used to using the so-called “Pyplot API” (i.e., Matlab-style plt.[something] commands), you should find this intuitive, and most of the commands have similar names. (One place you may have seen Axis objects before is in the result of “plt.gca()”.)
To create an axis, we use Canvas.add_axis()
. We must specify the name of
the axis (a unique identifier we will use to access that axis subsequently), the
lower left corner of the axis, and the upper right corner of the axis. For
example, to create an axis on Canvas c
named “myaxis” with lower left corner
an inch from the bottom left, and 1 inch high and one inch wide:
ax = c.add_axis("myaxis", Point(1, 1, "in"), Point(2, 2, "in"))
Now, ax
will be the axis object, and the canvas will have an empty axis on
it. To see this, run any of the visualization routines listed above, such as:
c.show()
Since ax
is a Matplotlib axis object, we can
plot to it the same way we would normally do in Matplotlib. For example, we can
add a scatterplot:
ax.scatter(np.random.rand(10), np.random.rand(10))
c.show()
We can also access axis objects after we make them. For axis “myaxis”, use
c.ax("myaxis")
to access the axis. A common paradigm in CanD is to declare
axes at the beginning, and then use them later. For instance:
c = Canvas(5, 5, "in")
c.add_axis("myaxis1", Point(1, 1, "in"), Point(2, 2, "in"))
c.add_axis("myaxis2", Point(3, 1, "in"), Point(4, 4, "in"))
c.add_axis("myaxis3", Point(1, 3, "in"), Point(2, 4, "in"))
[...]
ax = c.ax("myaxis1")
[...]
ax = c.ax("myaxis1")
[...]
ax = c.ax("myaxis1")
[...]
It is also possible to use this function to call axes directly. For instance:
c = Canvas(5, 5, "in")
c.add_axis("myaxis1", Point(1, 1, "in"), Point(2, 2, "in"))
c.ax("myaxis1").plot([1, 2, 3], [1, 2, 3])
Units are automatically created to go along with any new axis we create. In particular, two units are created. If the axis is named “myaxis”, then the two units are
Name identical to the axis name (in this case, “myaxis”): These are the data coordinates of the axis. If we plot the point (123, 456) on the axis (e.g., in a scatter plot), then this point will respond to wherever that point happens to be, adjusting for the x- and y-axis limits. Note that this uses the position where the data coordinate is located at the time the unit is used, rather than the time when the plot is displayed.
The axis name prepended with “axis” (in this case, “axis_myaxis”): These coordinates are relative to the location of the axis. The origin (0,0) is located at the bottom left corner of the axis, and the point (1,1) is located at the upper right corner of the axis.
These units can be used in exactly the same way as above. We will see an example of this below.
Text¶
In its simplest form, we can add text using the command
Canvas.add_text()
. The first argument is the text we would like to show,
followed by the position of the text. We may optionally specify alignment
through the optional ha
or horizontalalignment
and va
or
verticalalignment
arguments. The size
argument specifies font size,
style
can be set to italic
, and weight
can be set to bold
. Math
and unicode can be used as normal. For example:
c = Canvas(3, 3, "in")
c.set_default_unit("figure")
c.add_text("Center", Point(.5, .5))
c.add_text("Bottom left", Point(0, 0), ha="left", va="bottom", style="italic")
c.add_text("Upper right", Point(1, 1), size=20, ha="right", va="top", weight="bold", style="italic")
c.add_text(r"$\int_0^{10} x^\alpha$", Point(.25, .75))
c.add_text("Юникод", Point(.75, .25), weight="bold")
Changing the font¶
CanD has implemented a system (called “Fontant”) for selecting fonts which improves upon Matplotlib’s. In Matplotlib, fonts are selected with a “best guess” at what you meant. It can sometimes be difficult to choose between similar versions of the same font, or to find the correct name for the font you would like to use. Additionally, if there is a slight difference in the name you specified vs the actual name of the font (e.g., if you specified “Helvetica” instead of “Helvetica Std”), Matplotlib will fall back to the default font. Additionally, Matplotlib is inequipped to deal with fonts with different varieties. For example, sometimes it will randomly substitute stylistic alternatives of fonts you have selected, when multiple fonts match.
CanD improves upon this system in two ways. First, CanD is better able to guess what you meant than Matplotlib. It uses a more sophisticated algorithm for guessing the font name and the default version of the font. Second, if there is ever any ambiguity in the font selection, CanD will throw an error and ask you to be more specific. Additionally, CanD’s font management system will synchronize fonts across the document, including math fonts. In summary, CanD’s font management is unlikely to make surprising font choices.
We can specify this to Canvas.add_text()
using the font
argument.
Then, we disambiguate the font with further arguments. For example, if you run
the following:
c = Canvas(3, 3, "in")
c.add_text("Hello", Point(.5, .5, "figure"), font="Lucida")
you may receive the following error:
cand.fontant.MultipleFontsFoundError: Please be more specific in specifying font family.
Specify one of the following font names:
"Lucida Bright", "Lucida Calligraphy", "Lucida Console", "Lucida Fax", "Lucida Handwriting", "Lucida Math Std", "Lucida Sans Std", "Lucida Sans Typewriter", "Lucida Sans Typewriter Std", "Lucida Sans Unicode", "Lucida Std", "Lucida Typewriter Std"
Since there are multiple fonts which include the name “Lucida”, but none is a perfect match, we need to specify which one we want. We can fix this by specifying which font we want, changing the code to:
c.add_text("Hello", Point(.5, .5, "figure"), font="Lucida Console")
Sometimes, there may be multiple versions of a font. For instance, suppose we try to use Inconsolata:
c.add_text("Hello", Point(.2, .2, "figure"), font="Inconsolata")
This gives the following error:
cand.fontant.MultipleFontsFoundError: Please specify a stretch using the function argument stretch=[value]. Valid values for this font are:
"expanded", "ultracondensed", "ultraexpanded", "condensed", "semiexpanded", "extraexpanded", "extracondensed", "normal", "semicondensed"
Thus, we need to choose a stretch value for this font from the list. Specifying the stretch fixes the problem:
c.add_text("Hello", Point(.2, .2, "figure"), font="Inconsolata", stretch="condensed")
Additionally, some fonts may offer additional features beyond the default. For instance, Raleway provides more weights beyond “bold”:
c = Canvas(2, 3, "in")
for i,weight in enumerate(["thin", "extralight", "light",
"regular", "medium", "semibold",
"bold", "extrabold", "black"]):
c.add_text(weight, Point(1, 2.5-i/4, "in"), weight=weight, font="Raleway", style="normal")
To make sure fonts are consistent across the document, including axis tick
labels, we can use the Canvas.set_font()
function. This also allows the
ticksize
argument for setting the size of tick labels. For example, to set
the entire figure to be Helvetica with 6pt font and 5pt font for axis tick
labels, use one of the following, depending on which version of Helvetica you
have installed:
c.set_font("Nimbus Sans", size=6, ticksize=5)
c.set_font("Helvetica", size=6, ticksize=5)
c.set_font("Helvetica", stretch="normal", size=6, ticksize=5)
To add together everything we’ve learned so far about text and fonts, let’s create a labeled scatterplot showing the number of fingers vs the number of heart chambers across animals:
from cand import Canvas, Point, Vector
import seaborn as sns
import pandas
# Use a 4in x 4in canvas
c = Canvas(4, 4, "in")
# Use "Lucida Handwriting" as the default font for the entire plot.
c.set_font("Lucida Hand", size=14)
# We will only use one axis in this figure.
ax = c.add_axis("fin_v_cham", Point(.2, .2), Point(.7, .9))
# padding is the offset from text to figure label
padding = Vector(.2, .1, "cm")
# Let's use these example data
df = pandas.DataFrame({"fing": [5, 1, 4, 0, 0],
"cham": [4, 4, 3, 3, 2],
"anim": ["human", "horse", "frog", "snake", "fish"]})
ax.scatter(df["cham"], df["fing"], c='k', marker='x')
# For each of our animals, show the animal's name next to the data point
for row in df.iterrows():
c.add_text(row[1]['anim'], Point(row[1]['cham'], row[1]['fing'], "fin_v_cham")+padding, ha="left")
# Finish off the plot and display
ax.set_ylabel("# fingers")
ax.set_xlabel("# chambers in heart")
sns.despine(ax=ax)
c.show()
Geometric shapes¶
Geometric shapes can be added to any plot by specifying them with Points and Vectors. These are similar to several functions built into matplotlib, but the matplotlib functions do not support specifying positions using Points and Vectors.
Points and lines¶
A point or marker, similar to one that would be drawn in a Matplotlib
scatterplot, can be added with Canvas.add_marker()
. The first argument
is the position, and the remaining arguments are identical to those of Lines2D.
Likewise, a line can be drawn with Canvas.add_line()
. The first two
arguments are Points specifying the endpoints of the line, and the remaining
arguments are identical to those of Lines2D.
For example:
c = Canvas(2, 2, "in")
c.add_marker(Point(.5, .25), marker="*", markersize=12, color='g')
c.add_marker(Point(.5, .75), marker="o", markersize=12)
c.add_line(Point(0, .5), Point(1, .5), linewidth=3, color="r")
Note that these are not intended replace normal matplotlib plotting functions. When plotting on axes, it is usually more convenient to use the standard matplotlib “plot” and “scatter” functions.
Geometric shapes¶
Rectangles can be specified using the Canvas.add_rect()
function by
providing two Points as corners, the lower left and the upper right. All subsequent
arguments are identical to those for matplotlib.patches.Polygon
Polygons in general can be drawn with Canvas.add_polygon()
, where the
first argument is a list of Points which serve as the vertices of the polygon.
All subsequent arguments are identical to those for matplotlib.patches.Polygon
Notably, if you would like to draw an open polygon, use the “closed” argument.
It is also possible to draw “fancy boxes”,
such as those with rounded corners, jagged edges, or shapes which look like
giant arrows. These utilize the Canvas.add_polygon()
function. The
first to arguments are Points, specifying the lower left and upper right
corners. All subsequent arguments are passed to
matplotlib.patches.FancyBboxPatch.
We can draw circles and ellipses as well with Canvas.add_ellipse()
. We
specify them using the lower left and upper right point, which serves as their
bounding box. Additional arguments are identical to those for
matplotlib.patches.Ellipse.
For example:
c = Canvas(4,3,"in")
c.add_rect(Point(.2, .7, "in"), Point(3.8, .9, "in"), color='k')
c.add_box(Point(.2, .2, "in"), Point(3.8, .4, "in"), color='k', boxstyle='round')
c.add_box(Point(.5, 1.8, "in"), Point(1.5, 2.2, "in"), boxstyle="rarrow", fill=True, color=(.3, .7, .1))
c.add_poly([Point(3.1, 2.1, "in"), Point(3.3, 2.8, "in"), Point(2.9, 2.7, "in")], color='k')
c.add_ellipse(Point(2.1, 2.1, "in"), Point(2.3, 2.3, "in"), color='r')
c.add_ellipse(Point(2.5, 1.2, "in"), Point(3.8, 1.5, "in"), fill=False, linestyle='--', edgecolor='g')
Arrows¶
Arrows can be added just like lines. The arrow goes “from” the first argument and “to” the second argument, which are both Points. Subsequent arguments are identical to those passed to matplotlib.patches.FancyArrowPatch. Since FancyArrowPatch does not provide the most intuitive syntax, a few examples are given below:
c = Canvas(2.5, 4.5, "in")
h = 4.0
c.add_text("Default", Point(.5, h, "in"))
c.add_arrow(Point(1, h, "in"), Point(2, h, "in"))
h = 3.5
c.add_text("Bar arrow", Point(.5, h, "in"))
c.add_arrow(Point(1, h, "in"), Point(2, h, "in"),
arrowstyle="|-|,widthA=4,widthB=4", shrinkA=0, shrinkB=0)
h = 3.0
c.add_text("Filled head", Point(.5, h, "in"))
c.add_arrow(Point(1, h, "in"), Point(2, h, "in"),
arrowstyle="-|>,head_width=6,head_length=6", lw=4, capstyle="butt")
h = 2.5
c.add_text("Angled", Point(.5, h, "in"))
c.add_arrow(Point(1, h-.10, "in"), Point(2, h+.10, "in"),
connectionstyle="angle,angleA=90,angleB=0")
h = 2.0
c.add_text("Curved", Point(.5, h, "in"))
c.add_arrow(Point(1, h-.10, "in"), Point(2, h+.10, "in"),
connectionstyle="arc3,rad=.1")
h = 1.5
c.add_text("Wedge", Point(.5, h, "in"))
c.add_arrow(Point(1, h, "in"), Point(2, h, "in"),
arrowstyle="wedge,tail_width=10")
h = 1.0
c.add_text("Simple", Point(.5, h, "in"))
c.add_arrow(Point(1, h, "in"), Point(2, h, "in"),
arrowstyle="simple,head_width=16,tail_width=6,head_length=10")
h = 0.5
c.add_text("Fancy", Point(.5, h, "in"))
c.add_arrow(Point(1, h, "in"), Point(2, h, "in"),
arrowstyle="fancy,head_width=10,tail_width=8,head_length=6", color="green")
Note that, for compatibility with matplotlib, we respect the “shrinkA” and “shrinkB” arguments, which means the arrow will not connect directly to the points you provide. Setting “shinkA=0” and “shrinkB=0” will ensure arrows are connected to the specified Points.
Images¶
Images in .png or .pdf format can be positioned in the plot just like any other
plot element using the Canvas.add_image()
method. The first argument is
the filename, specified in either relative or absolute path. The second
argument is the position, specified as a Point. The relationship to the
position is specified by further arguments. Unlike other plot elements, images
are not given by their lower left and upper right coordinates. This is because,
in general, we would probably like to maintain the image’s aspect ratio. Thus,
we can specify an alignment with respect to the position
(horizontalalignment
or ha
for short, or verticalalignment
, va
for short) coupled with either a height or width argument. The horizontal
alignment can be left
, center
, or right
. The vertical alignment can
be top
, center
, or bottom
. The height
and width
should be
Vectors with 0 in the x or y dimension, respectively. Transparency is handled
automatically.
It is also possible to define both the height and the width of the image. This causes the image to be rescaled to match the specified dimensions.
Often, it can be useful to treat the image as a unit of measure, with (0,0) at
the lower left corner and (1,1) at the upper right corner. The optional
argument unitname
can be used to define a unit based on this image.
For example:
from urllib.request import urlretrieve
urlretrieve("https://raw.githubusercontent.com/mwshinn/CanD/master/cand-logo.png", "_logo.png")
c = Canvas(20, 8, "cm")
c.add_image("_logo.png", Point(1, 1, "cm"), ha="left", va="bottom", width=Vector(3, 0, "cm"))
c.add_image("_logo.png", Point(9, 4, "cm"), ha="center", va="center", height=Vector(0, 6, "cm"), unitname="middleimg")
c.add_image("_logo.png", Point(16, 4, "cm"), ha="left", va="center", width=Vector(2, 0, "cm"), height=Vector(0, 7, "cm"))
c.add_rect(Point(-.05, -.05, "middleimg"), Point(1.05, 1.10, "middleimg"), fill=None, linewidth=3)
c.add_text("Middle image", Point(.5, 1.05, "middleimg"), weight="bold", size=12)
Note that, unlike other plot elements, images are always on top. So it is not possible to overlay other plot elements on top of images.
Plot elements¶
CanD implements its own helper functions for several plot features. Some of these are reimplemented from matplotlib features. It is still possible to use the original matplotlib versions, but in many cases, the versions implemented by CanD will be simpler.
To add a legend, use the Canvas.add_legend()
function. The first
argument pos_tl
is the position of the top left corner, and the second
argument els
is a list with a specific format to describe the content of the
legend. Each element of the list should be a tuple, where the first element is
the title, and the second element is a dictionary to describe the style. The
elements of this dictionary should correspond to those passed to the
Canvas.add_line()
or Canvas.add_marker()
functions. To use a
marker instead of a line, set linestyle to the string “None”. You can
optionally pass additional arguments to control the spacing of the different
aspects of the legend. line_spacing determines spacing between each line of
descriptive text in the legend. sym_width is the width of the symbols (lines
and markers). padding_sep is the separation between the symbols and the
descriptive text.
To add a colorbar, use Canvas.add_colorbar()
. The first argument is the
name of the colorbar. This should be unique, and should not coincide with the
name of an axis, because this will be usable as a unit. The following two
arguments are the bottom left and upper right corners of the colorbar. The next
argument is a tuple containing the minimum and maximum value of the colorbar.
This colorbar function does not automatically map to a matplotlib axis, so the
axis limits (vmin and vmax) will have to be manually specified in both cases.
All remaining optional arguments are identical to those of matplotlib’s
ColorbarBase,
notably, cmap
, which takes the name of a colormap to use for the colorbar.
Orientation is determined automatically.
Additionally, labels can be added in a consistent manner with
Canvas.add_figure_labels()
. The first argument is a list of tuples
describing the labels to add. The first element of each tuple is the text to
use for the label, such as “a”, “b”, etc. The second element of each tuple is
the name of the axis to which to add the label. The third element of the tuple
is optional, and specifies an offset in the position. Following the argument,
Canvas.add_figure_labels()
function also takes an optional second
argument specifying the font size of the labels.
Grids of axes¶
Often, it is useful to align axes into a grid formation. This is provided for
convenience by the CanD method Canvas.add_grid()
. CanD’s functionality
is distinct from that offered by matplotlib, and operates slightly differently.
The first argument is a list of names of axes to be included in the grid. The
second argument specifies the number of rows in the grid. (The number of
columns will be auto-detected.) The third and fourth arguments specify the
lower left and upper right corners of the entire grid. The remaining
argument(s) specify the spacing between axes. This can be specified using the
size
argument, a single Vector which specified the size of the elements in
the grid. Alternatively, the spacing
argument is a Vector specifying how
much space to leave between axes for both the x and y dimensions. It is also
possible to mix these two styles: the size_x
argument can be used with the
spacing_y
argument, and the spacing_x
argument with the size_y
argument. Arguments with the _x
suffix take a Vector with 0 in the y
component, and those with a _y
suffix take a Vector with 0 in the x
component. Thus, for specifying the size of the elements in the grid, the
following argument(s) are possible:
spacing
(a Vector)size
(A Vector)spacing_x
(A Vector with 0 in the y direction) andsize_y
(A Vector with 0 in the x direction)spacing_y
(A Vector with 0 in the x direction) andsize_x
(A Vector with 0 in the y direction)
If None
is specified as the name of an axis, a blank space will be drawn
instead of the axis.
The following example illustrates these concepts:
c = Canvas(10, 10, "cm")
c.add_grid(["a", "b", "c"], 1, Point(1, 7, "cm"), Point(9, 9, "cm"), spacing=Vector(1, 0, "cm"))
c.add_grid(["d", "e", "f", "g", None, "h", "i", "j"], 3, Point(1, 1, "cm"), Point(5, 6, "cm"), size=Vector(.8, .8, "cm"))
c.add_grid(["k", "l", "m", "n"], 2, Point(6, 1, "cm"), Point(9, 6, "cm"), size_x=Vector(1, 0, "cm"), spacing_y=Vector(0, 1, "cm"))
for letter in "abcdefghijklmn":
c.add_text(letter, Point(.5, .5, "axis_"+letter), weight="bold")
Often, it can be useful to refer to a grid as a single object. For example, you
may want to insert text centered on the entire grid, or a legend a certain
distance to the right of the grid. The optional argument unitname
can be
used to define a unit based on this grid, where the origin (0,0) is the bottom
left corner of the bottom left axis in the grid, and (1,1) is the upper right
corner of the upper right axis in the grid.
While CanD does not have a function to directly specify sub-grids, these are
easy to implement using the Canvas.add_grid()
method through the use of a
dummy axis. For example:
c = Canvas(10, 10, "cm")
c.add_grid(["a", "b", "c", "dummy"], 2, Point(1, 1, "cm"), Point(9, 9, "cm"), size=Vector(3.5, 3.5, "cm"), unitname="grid")
c.ax("dummy").axis("off")
c.add_grid(["d", "e", "f", "g"], 2, Point(0, 0, "axis_dummy"), Point(1, 1, "axis_dummy"), size=Vector(1.25, 1.25, "cm"))
c.add_text("Our grid", Point(.5, 1.0, "grid")+Vector(0, .5, "cm"), size=10, weight="bold")
Saving¶
To save, call the Canvas.save()
method. The only mandatory argument is
the filename. Both png and pdf outputs are supported, which will be
auto-detected from the filename. The optional dpi
argument determines the
resolution of the output image, i.e., how many pixels per inch. It is most
useful for png files, but also for pdf files where axes have been rasterized.
All further arguments are passed to the matplotlib function savefig.