21

I'm playing with the idea of adding a chart in the begining of my thesis, showing the relative dimension of each chapter (or section, maybe). This is something that I would want to generate automatically.

Is there a package somewhere that already allows me to do this?

... or, failing that ...

Is there any way I can automatically determine the number of pages of each chapter/section, so that I can feed that data to an external tool to generate the chart?

Here's an example of how the chart could be displayed:

Chart of the relative dimension of each chapter

  • 3
    I don't know how to do it, but it would seem that the information used to build the table of contents (the .aux file) would contain the data needed to construct it. – Steven B. Segletes Jan 24 '14 at 15:20
  • I'm not sure what is the format of .aux files, but just gave a quick look at mine and I can't find any reference to page numbers. I'm guessing there's some additional information that is used to build the table of contents. – Filipe Correia Jan 24 '14 at 15:37
  • @FilipeCorreia: Which TeX processor are you using? LaTeX, pdfTeX, LuaTeX, etc? – Matthew Rankin Jan 24 '14 at 15:48
  • @MatthewRankin I'm using pdflatex, which I think uses pdfTex under the hood. – Filipe Correia Jan 24 '14 at 15:53
  • 4
    For example, in the .aux file, an entry such as \@writefile{toc}{\contentsline {subsection}{\numberline {1.1}AA}{1}} would indicate that subsection 1.1, entitled "AA", begins on page 1. – Steven B. Segletes Jan 24 '14 at 15:57
12

This is what I got:

output

With this code:

\documentclass{article}
\usepackage{pgfplots,tikz,refcount,nameref,totcount,etoolbox}
\usepackage{lipsum}

%%%% Must be placed in the preamble %%%
\newcounter{PlotMeCounter}
\newcommand{\plotted}{\stepcounter{PlotMeCounter}\label{plotme:sec\arabic{PlotMeCounter}}}
\makeatletter
\newcommand{\lastplotted}{
    \protected@write\@mainaux{}{
        \csgdef{totalplotted}{\arabic{PlotMeCounter}}
    }\plotted\label{plotme:secEnd}
}
\makeatother
\providerobustcmd{\totalplotted}{1}
%%% </Must be placed in the preamble> %%%

\begin{document}

%%% Can be placed anywhere %%%
\noindent\begin{minipage}{\textwidth}
\definecolor{chap1}{HTML}{6095C9}
\definecolor{chap2}{HTML}{CD665F}
\definecolor{chap3}{HTML}{AAC46C}
\definecolor{chap4}{HTML}{927AB1}
\begin{tikzpicture}[node distance=1em]
\pgfmathsetmacro{\firstpage}{\getpagerefnumber{plotme:sec1}}
\pgfmathsetmacro{\numpages}{max(1,\getpagerefnumber{plotme:secEnd}-\firstpage)}
\foreach \x [evaluate={\this=\getpagerefnumber{plotme:sec\x}-\firstpage},
             evaluate={\nextx=int(\x+1)},
             evaluate={\next=\getpagerefnumber{plotme:sec\nextx}-\firstpage}]
         in {1,...,\totalplotted} {
\draw [fill=chap\x,draw=chap\x!60!black,thick]
   (\textwidth/\numpages*\this, 0) rectangle
   (\textwidth/\numpages*\next, 0.6);
}
\end{tikzpicture}\\
{\sffamily
\foreach \x in {1,...,\totalplotted} {
  \tikz{\filldraw[draw=black,fill=chap\x] (0,0) rectangle (0.5em,0.5em);}
  \nameref{plotme:sec\x}\quad
}}
\end{minipage}
%%% </Can be placed anywhere> %%%


\section{Introduction}\plotted
\lipsum

\section{Some Random Chapter}\plotted
\lipsum
\lipsum

\section{Another Chapter}\plotted
\lipsum
\lipsum
\lipsum
\lipsum
\lipsum
\lipsum
\lipsum
\lipsum
\lipsum
\lipsum

\section{Conclusion}\plotted
\lipsum
\lipsum
\lipsum

\lastplotted

\end{document}

How to use it:

  • Place a \plotted after each chapter that should appear on the list. The first occurence will be page 0 on the graph. This way things like the titlepage or the table of contents won't be part of the graph.
  • Put a \lastplotted after the end of the last chapter that should appear in the plot, to ignore the appendix.
  • Define more colors if you have more than 4 chapters

How it works:

  • \plotted inserts \label{plotme:sec<counter>}, you can later get the page number where the label was defined using \getpagerefnumber (note that \pageref does not work inside pgfmath expressions).

  • \lastplotted saves the last value of the counter into the aux file so that in the next pdflatex pass \totalplotted is defined as the total number of entries. If there's no aux file because the previous pass failed, \totalplotted defaults to 1, this is important as it's used as a loop variable in the tikz graphic.

  • With this infos, plotting the graph is relatively straightforward, using \foreach, \getpagerefnumber and \totalplotted. We also iterate over colors, so make sure that the color chap is defined for all values of the counter.

  • As written in the comments, the calculations performed in \textwidth*\this/\numpages are fairly inaccurate as they rely on TeX-Dimensions. So numpages*textwidth (normally between 300 and 400) must not exceed 16383.99999, thus it only works for less than 40 pages. By dividing first, \textwidth/\numpages*\this, we get rid of the overflow, but we loose accuracy. Assuming a 1000 page document, we only have 4 significant digits left which may bad if you want to design the perfect thesis (the error has the size of approx. 0.01pt which could be measurable). There's also the option to use the fixedpointarithmetic library if you want to avoid that.

  • That's impressive! I'm getting an error with my document though. pdflatex says ! Dimension too large. <recently read> \pgfmath@x. Any idea what the cause might be? – Filipe Correia Jan 25 '14 at 1:18
  • @FilipeCorreia Thanks, I missed that. The problem was that \textwidth*\this/\numpages can overflow i.e. exceed 16383.99998 for large documents. After changing it to \textwidth/\numpages*\this it should work fine even for larger documents. – texnokrat Jan 25 '14 at 1:52
  • I'm accepting this solution. The markup on @cmhughes' solution is cleaner, but the final result of this one is closer to what I was looking for. Thanks! – Filipe Correia Jan 26 '14 at 22:52
  • After almost a year, I just found two issues with this solution: a) any idea why I can't have a \& in a chapter title with this code? :-) b) The legend wraps if the with exceeds the length of the Figure, but not always. It's not working for me for chapters with an exceptionally long title (~40 chars). – Filipe Correia Jan 21 '15 at 0:39
11

Update:

Most of the idea exposed in the initial version is now made automatically, adding support for unnumbered chapters:

\documentclass[openany]{book}
\usepackage{chappg}
\usepackage{pgfplots}
\usepackage{lipsum}
\usepackage{nameref}

\usepackage{showkeys}

\definecolor{ylgnbu1}{RGB}{255, 255, 204}
\definecolor{ylgnbu2}{RGB}{161, 218, 180}
\definecolor{ylgnbu3}{RGB}{65, 182, 196}
\definecolor{ylgnbu4}{RGB}{37, 52, 148}
\definecolor{ylgnbu5}{RGB}{44, 127, 184}
\definecolor{ylgnbu6}{RGB}{80, 40, 84}

\pgfplotsset{compat=1.9}
\pgfplotsset{
   /pgfplots/bar  cycle  list/.style={/pgfplots/cycle  list={%
        {black,fill=ylgnbu1,mark=none},%
        {black,fill=ylgnbu2,mark=none},%
        {black,fill=ylgnbu3!70,mark=none},%
        {black,fill=ylgnbu4!20,mark=none},%
        {black,fill=ylgnbu5!70,mark=none},%
        {black,fill=ylgnbu6!70,mark=none},%
     }
   },
}

\newcounter{tmp}
% definition of 100 auxialiary counters
\makeatletter
\loop
\stepcounter{tmp}
\ifnum\value{tmp}<101
  \newcounter{nochappg\roman{tmp}}
\repeat
\newcounter{nochappg}
\usepackage{etoolbox}
\setcounter{tmp}{0}
\pretocmd{\chapter}{\setcounter{nochappg\roman{tmp}}{\value{page}}}{}{}

\def\@makechapterhead#1{%
  \vspace*{50\p@}%
  {\parindent \z@ \raggedright \normalfont
    \ifnum \c@secnumdepth >\m@ne
      \if@mainmatter
        \huge\bfseries \@chapapp\space \thechapter
        \par\nobreak
        \vskip 20\p@
      \fi
    \fi
    \interlinepenalty\@M
    \Huge \bfseries #1\par\nobreak
    \vskip 40\p@
  }%
  \global\refstepcounter{tmp}%
  \label{chap:\arabic{tmp}}%
}
\def\@makeschapterhead#1{%
  \vspace*{50\p@}%
  {\parindent \z@ \raggedright
    \normalfont
    \interlinepenalty\@M
    \Huge \bfseries  #1\par\nobreak
    \vskip 40\p@
  }%
  \global\refstepcounter{tmp}%
  \label{chap:\arabic{tmp}}%
}

\renewcommand\chapter{%
  \setcounter{nochappg\roman{tmp}}{\value{page}}%
  \if@openright\cleardoublepage\else\clearpage\fi
  \thispagestyle{plain}%
  \global\@topnum\z@
  \@afterindentfalse
  \secdef\@chapter\@schapter}
\makeatother

\begin{document}

\chapter*{Intro}
\lipsum[4]

\chapter*{Preface}
\pagenumbering{bychapter}
\lipsum

\chapter{Test chapter one}
\lipsum

\chapter{Test chapter two}
\lipsum[1-30]

\chapter{Test chapter three}
\lipsum[1-80]

\chapter{Test chapter four}
\lipsum[1-15]

\chapter{Test chapter five}
\lipsum[1-30]

\setcounter{nochappg\roman{tmp}}{\value{page}}
\thenochappgv

\begin{tikzpicture}
\begin{axis}[
  height=\textwidth,
  ybar stacked,
  axis lines=none,
  nodes near coords,
  bar width=20pt,
  xmin=0,
  xmax=2,
  legend style={
    at={(0.75,0.55)},
    anchor=west,
    legend columns=2
  }
]
\addplot+[ybar] coordinates {(1,\thenochappgi)};
\addplot+[ybar] coordinates {(1,\thenochappgii)};
\addplot+[ybar] coordinates {(1,\thenochappgiii)};
\addplot+[ybar] coordinates {(1,\thenochappgiv)};
\addplot+[ybar] coordinates {(1,\thenochappgv)};
\addplot+[ybar] coordinates {(1,\thenochappgvi)};
\addplot+[ybar] coordinates {(1,\thenochappgvii)};
\legend{\nameref{chap:1},\nameref{chap:2},\nameref{chap:3},\nameref{chap:4},\nameref{chap:5},\nameref{chap:6},\nameref{chap:7}}
\end{axis}
\end{tikzpicture}

\end{document}

To do:

  1. Write the value for the counters in an auxiliary file..

  2. Automate the plot at the end.

enter image description here

A proof of concept:

\documentclass[openany]{book}
\usepackage{chappg}
\usepackage{pgfplots}
\usepackage{lipsum}
\usepackage{nameref}

\definecolor{ylgnbu1}{RGB}{255, 255, 204}
\definecolor{ylgnbu2}{RGB}{161, 218, 180}
\definecolor{ylgnbu3}{RGB}{65, 182, 196}
\definecolor{ylgnbu4}{RGB}{37, 52, 148}
\definecolor{ylgnbu5}{RGB}{44, 127, 184}

\pgfplotsset{compat=1.9}
\pgfplotsset{
   /pgfplots/bar  cycle  list/.style={/pgfplots/cycle  list={%
        {black,fill=ylgnbu1,mark=none},%
        {black,fill=ylgnbu2,mark=none},%
        {black,fill=ylgnbu3!70,mark=none},%
        {black,fill=ylgnbu4!20,mark=none},%
        {black,fill=ylgnbu5!70,mark=none},%
     }
   },
}

\newcounter{nochappgi}
\newcounter{nochappgii}
\newcounter{nochappgiii}
\newcounter{nochappgiv}
\newcounter{nochappgv}

\begin{document}

\chapter{Test chapter one}
\label{chap:one}
\lipsum
\setcounter{nochappgi}{\value{page}}
\thenochappgi
\chapter{Test chapter two}
\label{chap:two}
\lipsum[1-30]
\setcounter{nochappgii}{\value{page}}
\thenochappgiii
\chapter{Test chapter three}
\label{chap:three}
\lipsum[1-80]
\setcounter{nochappgiii}{\value{page}}
\thenochappgiii
\chapter{Test chapter four}
\label{chap:four}
\lipsum[1-15]
\setcounter{nochappgiv}{\value{page}}
\thenochappgiv
\chapter{Test chapter five}
\label{chap:five}
\lipsum[1-30]
\setcounter{nochappgv}{\value{page}}
\thenochappgiv

\begin{tikzpicture}
\begin{axis}[
  height=\textwidth,
  ybar stacked,
  axis lines=none,
  nodes near coords,
  bar width=20pt,
  xmin=0,
  xmax=2,
  legend style={
    at={(0.75,0.55)},
    anchor=west,
    legend columns=2
  }
]
\addplot+[ybar] coordinates {(1,\thenochappgi)};
\addplot+[ybar] coordinates {(1,\thenochappgii)};
\addplot+[ybar] coordinates {(1,\thenochappgiii)};
\addplot+[ybar] coordinates {(1,\thenochappgiv)};
\addplot+[ybar] coordinates {(1,\thenochappgv)};
\legend{\nameref{chap:one},\nameref{chap:two},\nameref{chap:three},\nameref{chap:four},\nameref{chap:five}}
\end{axis}
\end{tikzpicture}

\end{document}

The plot obtained shows a stacked bar plot corresponding to the number of pages of each chapter; the legend also contains the names for the chapters:

enter image description here

Some remarks:

  1. The idea is to use a mechanism similar to the one used by the chappg package, to get the number of pages for each chapter.
  2. This values are stored at the end of each chapter (this info can be written, for example, to the .aux file).
  3. Using the stored values, pgfplots is used to easily produce the desired plot.
4

Here's another solution that uses the \jobname.aux file

screenshot

Here's how it works:

  • every compilation writes a counter called totalchapters to the .aux file at the end of the document
  • each chapter command writes counters called totalpages<roman{chapter}> to the aux file; I've used roman{chapter} because LaTeX doesn't like having numbers in command or counter names
  • each compilation will, of course, read information back from the \jobname.aux file, so you'll need two compilations to get the right numbers.

The first time you run it, you should see the following in your jobname.log file:

Defining a new counter: totalpagesi
Defining a new counter: totalpagesii
Defining a new counter: totalpagesiii
Defining a new counter: totalchapters (3)

On the second run, you should see:

Total pages for Chapter 1 match auxilary file (3)
Total pages for Chapter 2 match auxilary file (6)
Total pages for Chapter 3 match auxilary file (13)
Total Chapters match auxilary file (3)

I haven't put the labels automatically on the chapters yet- you could lift that part from Gonzalo's answer, otherwise I'll pick it up again soon. Here's the code:

% arara: pdflatex
% !arara: indent: {overwrite: yes}
\documentclass{report}
\usepackage{etoolbox}
\usepackage{pgfplots}
\usepackage{lipsum}

\makeatletter
\newcommand{\totalchapters}[1]{%
    \@ifundefined{c@totalchapters}
    {%
        \newcounter{totalchapters}
        \setcounter{totalchapters}{#1}
        \typeout{Defining a new counter: totalchapters (#1)}
    }%
    {%
        \ifnum\value{totalchapters}=#1
        \typeout{Total Chapters match auxilary file (#1)}
        \else
        \typeout{Warning: total Chapter count updated from \the\value{totalchapters} to #1-- recompile to fix}
        \fi
        \setcounter{totalchapters}{#1}
    }%
}
\newcommand{\definetotalpagecount}[2]{%
    \@ifundefined{c@totalpages\@roman{#1}}%
    {%
        \newcounter{totalpages\@roman{#1}}
        \setcounter{totalpages\@roman{#1}}{#2}
        \typeout{Defining a new counter: totalpages\@roman{#1}}
    }%
    {%
        \ifnum\value{totalpages\@roman{#1}}=#2
        \typeout{Total pages for Chapter #1 match auxilary file (#2)}
        \else
        \typeout{Warning: total pages for Chapter #1 updated from \the\value{totalpages\@roman{#1}} to #2-- recompile to fix}
        \fi
        \setcounter{totalpages\@roman{#1}}{#2}
    }%
}


\preto\chapter{%
    \ifnum\value{chapter}>0
    \immediate\write\@auxout{%
        \string\definetotalpagecount\string{\thechapter\string}\string{\the\value{page}\string}
    }
    \fi
}

\AtEndDocument{%
    \immediate\write\@auxout{%
        \string\definetotalpagecount\string{\thechapter\string}\string{\the\value{page}\string}
        \string\totalchapters\string{\thechapter\string}%
    }
}

\newcommand{\drawPageChart}{%
    \begin{tikzpicture}
        \begin{axis}[
                xbar stacked,
                xmin=-0.1,
                %ymin=0,ymax=1,
                bar width=40pt,
                nodes near coords,
                axis lines=none,
                nodes near coords align={horizontal},
                visualization depends on=x \as \myxcoord,
                nodes near coords={\pgfmathprintnumber\myxcoord},
                every node near coord/.append style={
                    anchor=east},
            ]
            \@ifundefined{c@totalchapters}
            {}
            {%
                \foreach \i in {1,...,\thetotalchapters}{%
                    \addplot coordinates {(\the\value{totalpages\@roman{\i}},0)};
                }
            }
        \end{axis}
    \end{tikzpicture}
}
\begin{document}

\begin{figure}[!htb]
    \centering
    \drawPageChart
    \caption{Blueprint of my thesis}
\end{figure}


\chapter{}
\lipsum
\chapter{}
\lipsum
\lipsum
\chapter{}
\lipsum
\lipsum
\lipsum
\lipsum
\lipsum
\end{document}
  • I'm accepting @texnokrat's answer because his final result is much closer to what I was looking for, but this one wins the "cleanest markup" award :-) Well done. – Filipe Correia Jan 26 '14 at 22:52
  • @FilipeCorreia no worries :) I might come back to this and polish it a little in the future- glad you got a result you can use :) – cmhughes Jan 26 '14 at 23:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.