Author = "Tamas Nagy, Daniel C. Jones, Simon Leblanc"
Gadfly is an implementation of a "grammar of graphics" style statistical graphics system for Julia. This tutorial will outline general usage patterns and will give you a feel for the overall system.
Let us use Fisher's iris dataset as a starting point.
using Gadfly, RDatasets iris = dataset("datasets", "iris") set_default_plot_size(14cm, 8cm) # hide nothing # hide
When used with a DataFrame, the
plot function in Gadfly is of the form:
plot(data::AbstractDataFrame, elements::Element...; mapping...)
The first argument is the data to be plotted and the keyword arguments at the
end map "aesthetics" to columns in the data frame. All input arguments between
mapping are some number of "elements", which are the nouns and
verbs, so to speak, that form the grammar.
Let's get to it.
p = plot(iris, x=:SepalLength, y=:SepalWidth, Geom.point); nothing # hide
First note that we've taken advantage of the flexibility of Julia's handling of function signatures and put the keyword arguments in the midst of the positional arguments. This is purely for ease of reading.
The example above produces a
Plot object. It can be saved to a file by
drawing to one or more backends using
img = SVG("iris_plot.svg", 14cm, 8cm) draw(img, p) nothing # hide
Now we have the following charming little SVG image.
p # hide
If you are working at the REPL, a quicker way to see the image is to omit
the semi-colon trailing
plot. This automatically renders the image to
your default multimedia display, typically an internet browser. No need
to capture the output argument in this case.
plot(iris, x=:SepalLength, y=:SepalWidth)
Geom.point will be automatically supplied if no other geometries
Alternatively one can manually call
display on a
Plot object. This
workflow is necessary when
display would not otherwise be called
function get_to_it(d) ppoint = plot(d, x=:SepalLength, y=:SepalWidth, Geom.point) pline = plot(d, x=:SepalLength, y=:SepalWidth, Geom.line) ppoint, pline end ps = get_to_it(iris) map(display, ps)
For the rest of the demonstrations, we'll simply omit the trailing semi-colon for brevity.
In this plot we've mapped the x aesthetic to the
SepalLength column and the y
aesthetic to the
SepalWidth. The last argument, [
Gadfly.Geom.point), is a geometry element which takes bound aesthetics and
renders delightful figures. Adding other geometries produces layers, which may
or may not result in a coherent plot.
plot(iris, x=:SepalLength, y=:SepalWidth, Geom.point, Geom.line)
This is the grammar of graphics equivalent of "colorless green ideas sleep furiously". It is valid grammar, but not particularly meaningful.
If by chance your data are stored in Arrays instead of a DataFrame, fear not,
identical plots can be created using an alternate
Here, the keyword arguments directly supply the data to be plotted, instead of using them to indicate which columns of a DataFrame to use.
SepalLength = iris[:SepalLength] SepalWidth = iris[:SepalWidth] plot(x=SepalLength, y=SepalWidth, Geom.point, Guide.xlabel("SepalLength"), Guide.ylabel("SepalWidth")) nothing # hide
Note that with the Array interface, extra elements must be included to specify the axis labels, whereas with a DataFrame they default to the column names.
Let's do add something meaningful by mapping the color aesthetic.
plot(iris, x=:SepalLength, y=:SepalWidth, color=:Species, Geom.point); # or equivalently for Arrays: SepalLength = iris[:SepalLength] # hide SepalWidth = iris[:SepalWidth] # hide Color = iris[:Species] plot(x=SepalLength, y=SepalWidth, color=Color, Geom.point, Guide.xlabel("SepalLength"), Guide.ylabel("SepalWidth"), Guide.colorkey(title="Species"))
Ah, a scientific discovery: Setosa has short but wide sepals!
Color scales in Gadfly by default are produced from perceptually uniform colorspaces (LUV/LCHuv or LAB/LCHab), though it supports RGB, HSV, HLS, XYZ, and converts arbitrarily between these. Of course, CSS/X11 named colors work too: "old lace", anyone?
Scale transforms also work as expected. Let's look at some data where this is useful.
mammals = dataset("MASS", "mammals") plot(mammals, x=:Body, y=:Brain, label=:Mammal, Geom.point, Geom.label)
This is no good, the large animals are ruining things for us. Putting both axes on a log-scale clears things up.
plot(mammals, x=:Body, y=:Brain, label=:Mammal, Geom.point, Geom.label, Scale.x_log10, Scale.y_log10)
Since all continuous analysis is just degenerate discrete analysis, let's take a crack at the latter using some fuel efficiency data.
gasoline = dataset("Ecdat", "Gasoline") plot(gasoline, x=:Year, y=:LGasPCar, color=:Country, Geom.point, Geom.line)
We could have added [
explicitly, but this is detected and the right default is chosen. This is the
case with most of the elements in the grammar: we've omitted
Scale.x_continuous](@ref Gadfly.Scale.x_continuous) and
Scale.y_continuous](@ref Gadfly.Scale.y_continuous) in the previous plots,
as well as
Coord.cartesian, and guide elements such as
Guide.xticks](@ref Gadfly.Guide.xticks), [
Gadfly.Guide.xlabel), and so on. As much as possible the system tries to fill
in the gaps with reasonable defaults.
Gadfly uses a custom graphics library called Compose, which is an attempt at a more elegant, purely functional take on the R grid package. It allows mixing of absolute and relative units and complex coordinate transforms. The primary backend is a native SVG generator (almost native: it uses pango to precompute text extents), though there is also a Cairo backend for PDF and PNG. See Backends for more details.
Building graphics declaratively let's you do some fun things. Like stick two plots together:
fig1a = plot(iris, x=:SepalLength, y=:SepalWidth, Geom.point) fig1b = plot(iris, x=:SepalWidth, Geom.bar) fig1 = hstack(fig1a, fig1b)
Ultimately this will make more complex visualizations easier to build. For example, facets, plots within plots, and so on. See Compositing for more details.
The fuel efficiency plot is made more clear by toggling off some of the countries, for example. To do so, first render the plot using the SVGJS backend, which was not used to generate this webpage but is the default at the REPL, then simply click or shift-click in the colored squares in the table of keys to the right.
One can also zoom in and out by pressing the shift key while either scrolling the mouse wheel or clicking and dragging a box. Should your mouse not work, try the plus, minus, I, and O, keys. Panning is similarly easy: click and drag without depressing the shift key, or use the arrow keys. For Vim enthusiasts, the H, J, K, and L keys pan as expected. To reset the plot to it's initial state, double click it or hit R. Lastly, press C to toggle on and off a numerical display of the cursor coordinates.