Generating plots with pgfplots

Plotting results is often needed in Computer Science.
Say, you have a program that generates data which you want to plot.

A normal approach to this would be:

  1. Write your results to a csv-file
  2. Importing that file into a spreadsheet application like Excel
  3. Clicking around to generate a plot
  4. Saving the plot as an image
  5. Putting the image into a LaTeX report

In case you change something in your program, this entire process would have to be repeated in order to get your fresh results plotted and into your report.

How cumbersome. This is a stupid problem we can solve with programming.
If our program already generates data and writes it to disk in text format, we can just add functions that write that data as LaTeX!

Introducing PGFplots

PGFPlots is a nice package for LaTeX that does exactly what it says. It creates plots. There’s a gallery full of examples right here:
http://pgfplots.sourceforge.net/gallery.html
There are endless possibilities and this post will focus on generating plots from data generated by a program, not on the capabilities of pgfplots. Check out the main site for the package if you want to know how pgfplots works and what it can do.

http://pgfplots.sourceforge.net/

A pgfplot can look like this:

Rendered by QuickLaTeX.com

It is a made up example, plotting two data sets apples and oranges.

The code that generates this plot is:

Rendered by QuickLaTeX.com

Writing a program to do the hard work

We can break the structure of this into its discrete parts, identify the static parts and the changing parts.

All plots start and end with these two lines:

Rendered by QuickLaTeX.com

We then have a “preamble” with 8 changing parts: the height and width of the plot, the four coordinates defining the values in the coordinate system and the labels of the x and y axis:

        \begin{axis}[ %Axis defines the metadata for our plot
                height=9cm,%Height of the plot
                width=9cm,%Width of the plot
                grid=major,
                xmin=0,%The starting x-coordinate
                xmax=5,%The ending x-coordinate
                ymin=0,%The starting y-coordinate
                ymax=10,%The ending y-coordinate
                xlabel=Time,%The label on the x-axis
                ylabel=Number sold%The label on the y-axis
        ]
	... % the actual plot goes here
	\end{axis}

Last but not least we have the actual plot. For each thing we want to plot, we have a static part that always has to be there and a changing part, our data points and the label we want to go in the legend of the plot.

In my example, the labels are apples and oranges, each representing my made up data sets.

        \addplot coordinates { % The coordinates for apples
          (0.0, 0.0)
          (1.0, 2.0)
          (2.0, 3.0)
          (3.0, 5.0)
          (4.0, 7.0)
          (5.0, 3.0)
        };
        \addlegendentry{apples} % Add a label to the legend

Breaking this down further, we have

	\addplot coordinates {
% Coordinates, i.e. data from our program, goes here
};
	\addlegendentry{name of data for legend}

Writing functions that generates LaTeX for our plots is easy. We just need to inject our data into the static parts of a plot!

I will use F# for this but any language that can generate a string can be used.

First a long codedump that generates the example plot, then explanations of each part of the code:

let apples  = [(0.0, 0.0); (1.0,2.0); (2.0,3.0); (3.0,5.0); (4.0, 7.0); (5.0, 3.0)]
let oranges = [(0.0, 1.0); (1.0,3.0); (2.0,1.0); (3.0,5.0); (4.0, 4.0); (5.0, 8.0)]

/// All plots start and end with the same text.
/// This injects a string into that
let wrapInBeginAndEnd (s:string) =
    let beginString = @"

Rendered by QuickLaTeX.com

" sprintf "%s\n%s\n%s" beginString s endString /// All plots define an "axis", metadata for our plot /// This injects our parameters into that. let generateAxis height width xmin xmax ymin ymax xlabel ylabel = let beginAxis = @"\begin{axis}[" let h = sprintf "height=%dcm," height let w = sprintf "width=%dcm," width let grid = "grid=major," let xMin = sprintf "xmin=%d," xmin let xMax = sprintf "xmax=%d," xmax let yMin = sprintf "ymin=%d," ymin let yMax = sprintf "ymax=%d," ymax let xlbl = sprintf "xlabel=%s," xlabel let ylbl = sprintf "ylabel=%s," ylabel sprintf "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n]\n" beginAxis h w grid xMin xMax yMin yMax xlbl ylbl /// Generates the begin axis part, injecting the volatile parts height and width let wrapInAxis height width xmin xmax ymin ymax xlabel ylabel toWrap = let openAxis = generateAxis height width xmin xmax ymin ymax xlabel ylabel let closeAxis = @"\end{axis}" sprintf "%s%s\n%s\n" openAxis toWrap closeAxis /// Converts our list of data to newline-separated tikz-data let floatTupleListToTikzTuples (data: (float*float) list) = let convFun acc x = sprintf "%s%A\n" acc x List.fold convFun "" data /// wraps tikz-data in \addplot{...} and adds a legend entry let wrapCoords coordsAsString legendName = let addPlot = @"\addplot coordinates {" let legend = sprintf @"\addlegendentry{%s}" legendName sprintf "%s\n%s};\n%s\n" addPlot coordsAsString legend // Convert data to tikz data let applesCoords = floatTupleListToTikzTuples apples let orangesCoords = floatTupleListToTikzTuples oranges // Wrap data in \addplot{...} including legend entry name let wrappedApplesCoords = wrapCoords applesCoords "apples" let wrappedOrangesCoords = wrapCoords orangesCoords "oranges" // Joins the two generated plots let allCoordsAsTikz = sprintf "%s\n%s\n" wrappedApplesCoords wrappedOrangesCoords // Wraps it all in \begin{axis}...\end{axis} let withAxis = wrapInAxis 9 9 0 5 0 10 "Time" "Number sold" allCoordsAsTikz // wraps it all in

Rendered by QuickLaTeX.com

let finalLatex = wrapInBeginAndEnd withAxis printfn "%s" finalLatex

Not too bad. Now for the explanations.

We start by converting our data to something Tikz can understand. (Tikz is what pgfplots uses to draw the plots).

let floatTupleListToTikzTuples (data: (float*float) list)  =
    let convFun acc x = sprintf "%s%A\n" acc x
    List.fold convFun "" data 

We have our data as tuples of floats. F# lets us convert the tuple into a string, using the format string %A. We want to separate the elements by newline. We accomplish this with a fold over our list of data, generating the desired string for each element of our data.

We now want to wrap the plot coordinates in the
\addplot coordinates{…} part and add the label for our legend entry
in the
\addlegendentry{…} part.

let wrapCoords coordsAsString legendName =
    let addPlot = @"\addplot coordinates {"
    let legend = sprintf @"\addlegendentry{%s}" legendName
    sprintf "%s\n%s};\n%s\n" addPlot coordsAsString legend

Next we want to wrap that inside the
\begin{axis}…\end{axis} part. We need to generate the axis preamble before we can do that!

let generateAxis height width xmin xmax ymin ymax xlabel ylabel =
    let beginAxis =        @"\begin{axis}["
    let h         = sprintf "height=%dcm," height
    let w         = sprintf "width=%dcm," width
    let grid      =         "grid=major,"    
    let xMin      = sprintf "xmin=%d," xmin
    let xMax      = sprintf "xmax=%d," xmax
    let yMin      = sprintf "ymin=%d," ymin 
    let yMax      = sprintf "ymax=%d," ymax
    let xlbl      = sprintf "xlabel=%s," xlabel
    let ylbl      = sprintf "ylabel=%s," ylabel
    
    sprintf "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n]\n" beginAxis h w grid xMin xMax yMin yMax xlbl ylbl 

This function seems a bit messy but actually there is not much going on, just a lot of format strings.

The function takes these parameters as input:

  • height : int
  • width : int
  • xmin : int
  • xmax : int
  • ymin : int
  • ymax : int
  • xlabel : string
  • ylabel : string

Now that we can generate our axis preamble, we want to inject our plots in between
\begin{axis}[…] … \end{axis}.

let wrapInAxis height width xmin xmax ymin ymax  xlabel ylabel toWrap =
    let openAxis  = generateAxis height width xmin xmax ymin ymax xlabel ylabel
    let closeAxis = @"\end{axis}"
    sprintf "%s%s\n%s\n" openAxis toWrap closeAxis

We are almost done! We just need to wrap our generated string in between

Rendered by QuickLaTeX.com

.

The following piece of code does exactly that.

let wrapInBeginAndEnd (s:string) =
    let beginString = @"

Rendered by QuickLaTeX.com

" sprintf "%s\n%s\n%s" beginString s endString

Putting it all together, we end up with the many lines of code I showed earlier.
The generated LaTeX-string can be saved to disk and used in any LaTeX report.

Getting the plot into a LaTeX report

LaTeX has a command for including LaTeX files. This is useful if you are writing e.g. a book and want to split your chapters into separate files.
It is also useful to us, as we can save the generated plot as a .tex file and just include that.
If we have a LaTeX file called report.tex and in the same directory have a file called ourGeneratedPlot.tex, we can include the plot in our report in the following way:

\documentclass[12pt, a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage [T1]{fontenc}
\usepackage{pgfplots} % We of course need to include the package pgfplots to use it
\pgfplotsset{compat=1.13}
\title{Plotting with PGFPlots}
\author{Mads Obitsø}

\begin{document}
\maketitle

\input{ourGeneratedPlot} % insert the file ourGeneratedPlot.tex

\end{document}

No more clicking around in excel!

Happy generating plots.