In using Org tables, it is a natural desire to visualize the data captured in them. Org Plot is a built-in feature in Org to do just that without having to leave Emacs. That said, a couple of caveats with using Org Plot out of the box:
-
Without configuration, Org Plot output is ugly.
-
Making Org Plot attractive requires a lot of boilerplate code with unmemorable syntax.
Given the above, it seems easier to punt and export your table data to a spreadsheet to make plots. But why bother with two sources of truth when you can just work with one? This post endeavors to show that you can have beautiful and easy to make plots with Org Plot.
Unconvinced? Come back after watching the video demo below.
Want to know more? Read on…
Under the hood, Org Plot uses gnuplot to generate a graphic plot.
gnuplot is, like Emacs, a robust and highly configurable program. But like Emacs, gnuplot has many default settings that are less than desirable. The net result of this is that gnuplot without configuration produces ugly output. Don’t take my word for it – others say it here, here, here, and here.
That said, gnuplot can be easily configured to have attractive, daresay beautiful, output.
To generate a plot, the function org-plot/gnuplot
is invoked via either the key binding C-c " g
or calling M-x org-plot/gnuplot
. When org-plot/gnuplot
is first called, the gnuplot program is initialized and communicated to via the Emacs buffer ✷gnuplot✷
. From there org-plot/gnuplot
will translate the #+PLOT
options for a table into gnuplot commands.
Org Plot Out of the Box
Here’s an example of a table taken from the Org Plot documentation.
#+PLOT: title:"Citas" ind:1 deps:(2 3) type:2d with:histograms set:"yrange [0:]"
| Sede | Max cites | H-index |
|-----------+-----------+---------|
| Chile | 257.72 | 21.39 |
| Leeds | 165.77 | 19.68 |
| Sao Paolo | 71.00 | 11.50 |
| Stockholm | 134.19 | 14.33 |
| Morelia | 257.56 | 17.67 |
Running org-plot/gnuplot
will result in a GUI window plot as shown below:
A couple of observations at this point:
-
All the
PLOT
options are put into a single line, but they don’t have to be.Options can be enumerated over multiple lines for easier comprehension and maintainability.
-
The key binding for
org-plot/gnuplot
is quite difficult to remember.YMMV, but I’ll likely never remember
C-c " g
. -
The rendered plot is missing basic elements such as labels for the x and y axis.
There are also no affordances to change the typeface and size for different labels.
-
While the resultant plot is functional, thin lines and unfilled shapes is unattractive.
In the next section, we’ll show how to address these observations to get more attractive output from Org Plot.
Let’s sanity check the things we want to achieve with this post:
- Support quick creation of plots from an Org table that you can use and share with colleagues without embarrassment.
- Support line and column (called histogram in gnuplot) style plots.
- Support configuring basic plot features:
- plot title
- x and y axis labels
- typeface and sizes of the above
- Use
context-menu-mode
as the primary UI to Org Plot. - Not require associative recall of gnuplot commands and options to achieve the above.
Set the typeface, and typeface size
Org Plot provides a pass-through interface for the gnuplot command set
, which is used to set a multitude of gnuplot options. The line below shows how the typeface and typeface size can be configured via the
and
parameters. Setting these values will apply to all labels used in the plot.
#+PLOT: set:"termoption font ','"
For my taste, I’ll use Futura for
and 12pt for the
.
Alternately, one could configure the gnuplot terminal
and set graphic device, size, and font in a single command. For example on macOS:
#+PLOT: set:"term aqua size 846, 594 font 'Futura,12'"
Setting the
and
for the x and y axis labels are done via the gnuplot options xtics
and ytics
respectively.
#+PLOT: set:"xtics font ','"
#+PLOT: set:"ytics font ','"
If
is omitted, then the current font
option value will be used.
Set the histogram (column) style
We’ll configure the style of the histogram with the following code. Refer to the gnuplot documentation on histograms for more detail on syntax.
#+PLOT: set:"style histogram clustered gap 2"
#+PLOT: set:"style fill solid 1.0 border -1"
Set the Legend
The gnuplot option key
controls how the legend is rendered. Refer to the gnuplot documentation on key placement and key for more detail on syntax.
#+PLOT: set:"key right top"
Set the Axis Labels
The options xlabel
and ylabel
can be set with the appropriate string values for the plot at hand. Replace
and
accordingly.
#+PLOT: set:"xlabel ''"
#+PLOT: set:"ylabel ''"
Bring it all together
The following code brings all of the above together. The output result from running org-plot/gnuplot
is shown below.
#+PLOT: title:"Citas"
#+PLOT: set:"term aqua size 846, 594 font 'Futura,12'"
#+PLOT: type:2d
#+PLOT: set:"style histogram clustered gap 1"
#+PLOT: set:"style fill solid 1.0 border -1"
#+PLOT: set:"key right top"
#+PLOT: set:"xlabel 'Cities'"
#+PLOT: set:"ylabel 'Metrics'"
#+PLOT: set:"xtics font ',10'"
#+PLOT: set:"ytics font ',10'"
#+PLOT: set:"yrange [0:]"
#+PLOT: with:histograms
#+PLOT: ind:1 deps:(2 3)
| Sede | Max cites | H-index |
|-----------+-----------+---------|
| Chile | 257.72 | 21.39 |
| Leeds | 165.77 | 19.68 |
| Sao Paolo | 71.00 | 11.50 |
| Stockholm | 134.19 | 14.33 |
| Morelia | 257.56 | 17.67 |
Line Plot Example
If the plot is using line
style, you can specify the linewidth
to use. Here it is specified the value of 3 px.
#+PLOT: with:"lines linewidth 3"
Adjusting the PLOT
header accordingly, the source and resultant plot output is shown below.
#+PLOT: title:"Line Example"
#+PLOT: set:"term aqua size 846, 594 font 'Futura,12'"
#+PLOT: type:2d
#+PLOT: set:"key right top"
#+PLOT: set:"xlabel 'Some x metric'"
#+PLOT: set:"ylabel 'Some y metrics'"
#+PLOT: set:"xtics font ',10'"
#+PLOT: set:"ytics font ',10'"
#+PLOT: set:"yrange [0:]"
#+PLOT: ind:1 deps:(2 3)
#+PLOT: with:"linespoints linewidth 3 pointsize 2.0"
| x | y | z |
|---+---+---|
| 0 | 0 | 8 |
| 1 | 3 | 2 |
| 2 | 8 | 3 |
| 3 | 7 | 9 |
| 4 | 2 | 8 |
| 5 | 4 | 8 |
So at this point we have the desired control and aesthetics but at the cost of a lot of boilerplate PLOT
statements. To alleviate typing, we’ll use YASnippet, a template system that supports dynamic expansion. Among the key features it provides is the ability to prompt the user to choose different values and based on that value dynamically generate the expanded output.
For the histogram example above, the YASnippet template can be as follows:
# -*- mode: snippet -*-
# name: org-plot histogram
# --
#+PLOT: title:"${1:Line Example}"
#+PLOT: set:"term `(cond ((or (eq (window-system) 'mac) (eq (window-system) 'ns)) "aqua") ((eq (window-system) 'x) "x11") ((eq (window-system) 'w32) "windows"))` size 846, 594 font 'Futura,12'"
#+PLOT: type:2d
#+PLOT: set:"style histogram ${2:$$(yas-choose-value '("clustered" "rowstacked"))}${2:$(when (equal yas-text "clustered") " gap 1")}"
#+PLOT: set:"style fill solid 1.0 border -1"
#+PLOT: set:"key right top"
#+PLOT: set:"xlabel '${3:x label}'"
#+PLOT: set:"ylabel '${4:y label}'"
#+PLOT: set:"xtics font ',10'"
#+PLOT: set:"ytics font ',10'"
#+PLOT: set:"yrange [0:]"
#+PLOT: with:histograms
#+PLOT: ind:${5:1} deps:(${6:2})$0
The above snippet has logic to 1) dynamically choose the terminal type (term
), 2) tab
key to different fields to populate the plot’s title, axis labels, and histogram style, and 3) define which table column is independent (ind
) and which columns are dependent (deps
).
Note that different YASnippet templates can be created for different plot types. For this post we’ll only concentrate on variants of lines and histograms.
# -*- mode: snippet -*-
# name: org-plot lines
# --
#+PLOT: title:"${1:Line Example}"
#+PLOT: set:"term `(cond ((or (eq (window-system) 'mac) (eq (window-system) 'ns)) "aqua") ((eq (window-system) 'x) "x11") ((eq (window-system) 'w32) "windows"))` size 846, 594 font 'Futura,12'"
#+PLOT: type:2d
#+PLOT: set:"key right top"
#+PLOT: set:"xlabel '${3:x label}'"
#+PLOT: set:"ylabel '${4:y label}'"
#+PLOT: set:"xtics font ',10'"
#+PLOT: set:"ytics font ',10'"
#+PLOT: set:"yrange [0:]"
#+PLOT: ind:${5:1} deps:(${6:2})
#+PLOT: with:"${7:$$(yas-choose-value '("lines" "linespoints" "impulses"))} linewidth 3${7:$(when (equal yas-text "linespoints") " pointsize 2.0")}"$0
At this point we have some snippets but have