45

I have recently started using the tufte-latex package for various science writing and, while I love the package generally, am dismayed that it does not include the ability to produce graphics (charts, sparklines, etc.) in the style of Tufte. While his typesetting is wonderful, it seems to me that the plots are the primary focus of his work.

Is there a readily available package that allows for easy production of Tufte-style plots? Perhaps using pgfplots or sageTeX?

N.N.
  • 36,163
AdamRedwine
  • 1,133

3 Answers3

49

The pgfplots package can do a lot of plotting stuff.

In contrast to the simpler sparkline package, it supports arbitrary input data - which might be given by some math expressions or table data (which might be interesting for statistical applications). It also comes with pgfplotstable which allows some automatic rounding of such tables.

In fact, I believe that I came across a solution by someone who produced pretty sparklines side-by-side using pgfplots and some table solution (looked pretty cool). If someone happens to know what I saw, this might be an interesting solution approach here (but I do not know where it is).

From my point of view, a usable solution for sparklines with pgfplots would need:

  • some styles adjustments
  • a little bit code to place start- and end nodes (together with their coordinate values)
  • a little bit math to compute min and max.

Here is the the output of an experiment this evening:

enter image description here

The source code is

% This is an experiment with a sparkline implementation based on
% pgfplots.
%
\documentclass[a4paper]{article}

\usepackage{pgfplots}


% BEGIN OF STYLE DEFINITION FOR sparkline:
\makeatletter
\usetikzlibrary{positioning}
\colorlet{sparkcolor}{red!80!black}
\colorlet{minmaxcolor}{blue!80!black}
\pgfplotsset{
    every spark line/.style={
        hide axis,
        clip=false,
        bar width=2pt,
        cycle list={blue,mark=none\\},
        height=0.8\baselineskip,
        anchor=south west,
        width=5\baselineskip,
        scale only axis,
        filter point/.code={%
            \xdef\sparklinex{\pgfkeysvalueof{/data point/x}}%
            \xdef\sparkliney{\pgfkeysvalueof{/data point/y}}%
            \pgfplotscoordmath{default}{parsenumber}{\sparklinex}%
            \let\sparklinex@=\pgfmathresult
            \pgfplotscoordmath{default}{parsenumber}{\sparkliney}%
            \let\sparkliney@=\pgfmathresult
            \ifnum\coordindex=0
                \global\let\sparklineSTARTx\sparklinex
                \global\let\sparklineSTARTy\sparkliney
                \global\let\sparklineMINx\sparklinex
                \global\let\sparklineMINy\sparkliney
                \global\let\sparklineMINy@\sparkliney@
                %
                \global\let\sparklineMAXx\sparklinex
                \global\let\sparklineMAXy\sparkliney
                \global\let\sparklineMAXy@\sparkliney@
            \else
                \pgfplotscoordmath{default}{if less than}{\sparkliney@}{\sparklineMINy@}{%
                    \global\let\sparklineMINx\sparklinex
                    \global\let\sparklineMINy\sparkliney
                    \global\let\sparklineMINy@\sparkliney@
                }{}%
                \pgfplotscoordmath{default}{if less than}{\sparklineMAXy@}{\sparkliney@}{%
                    \global\let\sparklineMAXx\sparklinex
                    \global\let\sparklineMAXy\sparkliney
                    \global\let\sparklineMAXy@\sparkliney@
                }{}%
            \fi
            \global\let\sparklineENDx\sparklinex
            \global\let\sparklineENDy\sparkliney
        },
        extra description/.code={%
            \node[begin node description]  {\pgfkeysvalueof{/pgfplots/begin node text}};
            \node[end node description]    {\pgfkeysvalueof{/pgfplots/end node text}};
        },
    },
    spark std max/.initial=,
    spark std min/.initial=,
    /tikz/spark std region/.style={fill=gray!10},
    begin node text/.initial=\pgfmathprintnumber{\sparklineSTARTy},
    end node text/.initial=  \pgfmathprintnumber{\sparklineENDy},
    /tikz/begin node description/.style={
        text width=3em,align=right,
        base left,sparkcolor,
        /pgf/number format/fixed,
        /pgf/number format/fixed zerofill,
        /pgf/number format/showpos,
        at={(current axis.south west)},
    },
    /tikz/end node description/.style={
        base right,sparkcolor,
        /pgf/number format/fixed,
        /pgf/number format/fixed zerofill,
        /pgf/number format/showpos,
        at={(current axis.south east)},
    },
    sparkline nodes/.initial={%
        node[begin node] at (axis cs:\sparklineSTARTx,\sparklineSTARTy) {}
        node[end node]   at (axis cs:\sparklineENDx,\sparklineENDy) {}
        node[min node]   at (axis cs:\sparklineMINx,\sparklineMINy) {}
        node[max node]   at (axis cs:\sparklineMAXx,\sparklineMAXy) {}
    },
    /tikz/spark marker/.style={
        circle,
        inner sep=1pt,
    },
    /tikz/begin node/.style={
        spark marker,
        fill=sparkcolor,
    },
    /tikz/end node/.style={
        spark marker,
        fill=sparkcolor,
    },
    /tikz/min node/.style={
        spark marker,
        fill=minmaxcolor,
    },
    /tikz/max node/.style={
        spark marker,
        fill=minmaxcolor,
    },
}

\def\sparkline{\pgfutil@ifnextchar[{\sparkline@opt}{\sparkline@opt[]}}%
\long\def\sparkline@opt[#1]#2;{%
    \begin{tikzpicture}[baseline]
    \begin{axis}[every spark line,#1]
        \begin{scope}[yshift=-0.2\baselineskip]
        \pgfplotsextra{
            \pgfkeysgetvalue{/pgfplots/spark std min}\sparkstdmin
            \pgfkeysgetvalue{/pgfplots/spark std max}\sparkstdmax
            \ifx\sparkstdmin\empty
            \else
                \ifx\sparkstdmax\empty
                \else
                    \fill[spark std region]
                         (axis cs:\sparklineSTARTx,\sparkstdmax) rectangle (axis cs:\sparklineENDx,\sparkstdmin);
                \fi
            \fi
        }
        \addplot #2 \pgfkeysvalueof{/pgfplots/sparkline nodes};
        \end{scope}
    \end{axis}
    \end{tikzpicture}%
}

\makeatother
% END OF STYLE DEFINITIONS

\begin{document}

\thispagestyle{empty}

\pgfplotsset{
    spark std min=-0.5,
    spark std max=0.5,
    /pgf/number format/fixed,
    /pgf/number format/fixed zerofill,
    /pgf/number format/showpos,
}

Stock A \sparkline {rand} ; 
min = \textcolor{minmaxcolor}{\pgfmathprintnumber\sparklineMINy}, max = \textcolor{minmaxcolor}{\pgfmathprintnumber\sparklineMAXy}

Stock B \sparkline {rand} ;
min = \textcolor{minmaxcolor}{\pgfmathprintnumber\sparklineMINy}, max = \textcolor{minmaxcolor}{\pgfmathprintnumber\sparklineMAXy}

Stock C \sparkline table[row sep=\\] {
x y\\
0 0\\
0.1 0.1\\
0.2 0.6\\
0.3 -0.3\\
0.4 -1.5\\
0.5 -0.4\\
0.6 0\\
};
min = \textcolor{minmaxcolor}{\pgfmathprintnumber\sparklineMINy}, max = \textcolor{minmaxcolor}{\pgfmathprintnumber\sparklineMAXy}

Stock D \sparkline[ybar,samples=15] {rand} ; 
min = \textcolor{minmaxcolor}{\pgfmathprintnumber\sparklineMINy}, max = \textcolor{minmaxcolor}{\pgfmathprintnumber\sparklineMAXy}
\end{document}

My idea is to define a macro \sparkline <coordinate input> ; which expects one of the different <coordinate input>s of pgfplots, as \addplot {rand}; or \addplot table (which are used above). I should note that in my case above, the space between {rand} and ; is mandatory (due to some expansion issue).

My idea is to use \baselineskip to determine the sparkline's dimensions (which, however, appears to cause problems inside of tabular!?).

The rest of the user interface is given by the options spark std min and spark std max which configure the gray background area.

Within this solution, all other things could be configured by means of styles.

See also http://pgfplots.sourceforge.net/pgfplots.pdf

  • Great answer! Since there's examples of bar diagrams in the sparklines manual it might make sense that you included some in your answer too. – N.N. Sep 23 '11 at 19:26
  • I have added an example using bar plots. I also fixed a couple of bugs (like using fixed zerofill instead of fixed,fixed zerofill and showing the same start-and end coordinate in the texts). – Christian Feuersänger Sep 23 '11 at 19:39
  • Very interesting and useful, thanks. I might make use of some of that but I am still a bit disappointed that there isn't a coherent package with set defaults so I could just type something like \binary_plot{[x_1,x_2,x_3]} and get the plot he suggests for sports scores. I actually implemented this plot in a sage script, which can easily be incorporated with sageTeX, but I am not good enough at programing to make a serious go at a comprehensive package. – AdamRedwine Sep 23 '11 at 19:56
  • Perhaps there is some kind of "coherent package with set defaults" around which does the job correctly. You may want to wait before you accept the answer. Perhaps someone wrote one with pstricks? I am aware of the fact that my answer is more a starting point than a fully satisfactory solution (and, of course, only for sparklines). – Christian Feuersänger Sep 23 '11 at 20:11
  • Hi, I tried this today. The plotting works great and I look forward to use it in practice. Only the background gray bars seem off to me; or I don't understand how they are supposed to work. We set std min / max = 0.5 / -0.5. However the rectangle seems to start at 0 on the lower end. I experimented a bit and it appears that it has to do with negative numbers somehow. Do you have an idea? – Matthias Kauer Nov 02 '14 at 20:25
  • Actually, that's not it, but it does seem off. Setting min to -1.5 isn't flush with the -1.5 from Stock C for instance. Setting min to -2.0 makes the rectangle end at the -1.5 lowest point. Hmm. – Matthias Kauer Nov 02 '14 at 20:43
  • ok, I removed the yshift on the scope inside the plot macro and now the rectangle fits. I realize however that you have way more experience with this than I do, so I wonder why you put it there in the first place :) – Matthias Kauer Nov 02 '14 at 20:53
  • @MatthiasKauer thanks for the feedback and the solution. This answer has never been adapted to "production quality", so it might simply be a bug. Maybe you can collect some experience with it. I would appreciate feedback of sorts "hey, super tool, I consider it finished" or perhaps "okay... but it would need to do XYZ before I would really use it". Maybe it is time to consider next steps with it (i.e. write some production quality library out of it -- eventually). – Christian Feuersänger Nov 02 '14 at 21:10
  • Thank you so much for the explanation! I have only toyed with this so far, but it appears to be the most useable package out there. The others all require manual data input and this one profits from the csv reading that you put into pgfplots (and that I'm reasonably familiar with it) which puts it far ahead of the pack. I suppose I'll start using this and will see what comes up :) – Matthias Kauer Nov 02 '14 at 21:37
  • Thanks for this code, helped me a lot. One note on the \baselineskip, it's 0pt in tabular environments, so using \normalbaselineskip helps there. – sr_ Jun 24 '15 at 09:37
14

I'd suggest you go with Christian Feuersänger answer but I'd also like to note that there's the path egreg mentioned in a comment.

There's the package called sparklines.

\documentclass{tufte-handout}

\usepackage{sparklines}

\begin{document}

A sparkline
\begin{sparkline}{10}
\sparkrectangle 0.3 0.8
\sparkdot 0.5 0.62 blue
\sparkdot 1 0.2 red
\spark 0.1 0.95
0.6 0.7
0.2 0.8 0.3 0.3 0.4 0.52 0.5 0.62
0.7 0.5 0.8 0.4 0.9 0.25 1 0.2 /
\end{sparkline}.
and another
\begin{sparkline}{4}
\sparkspike .083 .18
\sparkspike .25 .55
\sparkspike .417 1
\sparkspike .583 .62
\sparkspike .75 .42
\sparkspike .917 .5
\end{sparkline}.

\end{document}

Two sparklines

N.N.
  • 36,163
  • Yeah, that is nice too, though I'd really love to see things like the rug plots and such. I'm better acquainted with sage than with pgfplots and I might implement a few of his plots. If they get good enough, I might post them somewhere. – AdamRedwine Sep 24 '11 at 12:24
  • 1
    @AdamRedwine If it were me I would make my plots in another software (R does rugplots nicely) and just includegraphics. After all, TeX doesn't specialise in graphics. – isomorphismes Sep 25 '13 at 05:54
5

Although it appears that there aren't any readymade packages specifically for LaTeX, depending on which math/graphics package you use, there might be alternatives.

For example, the sagemath program provides a ready interface to LaTeX through it's sageTeX module and renders 2D plots with the Matplotlib library. Options passed on to plots that do not have meaning in Sage will be passed to Matplotlib so it is then possible to use packages like etframes to get tufte style plots. Though I only recently came across this package, I'll add more if I find them as I do research.

AdamRedwine
  • 1,133