% File: numodel-plot-manual.tex
% Standalone manual for the numodel-plot package.

\documentclass{ltxdoc}
\usepackage[left=3.5cm, right=2cm, top=2cm, bottom=2cm,
            marginparwidth=3.5cm, marginparsep=0.3cm]{geometry}
\usepackage{numodel-plot}
\usepackage{subcaption}
\EnableCrossrefs
\CodelineIndex

% Fonts.  fontspec + unicode-math require LuaLaTeX.
\usepackage{fontspec}
\setmainfont{Arial}
\usepackage{unicode-math}
\setmathfont{Lete Sans Math}
\setmonofont{Fira Mono}

% Breakable inline verbatim (see numodel-manual.tex for rationale).
\usepackage{fvextra}
\AtBeginDocument{%
  \MakeShortVerb{\|}%
  \DeleteShortVerb{\|}%
  \DefineShortVerb[breaklines,breakanywhere]{\|}%
}

% tcolorbox+listings for example boxes that both display and execute.
\usepackage{tcolorbox}
\tcbuselibrary{listings,skins,breakable}

\lstdefinestyle{numodelcode}{
  basicstyle=\small\ttfamily,
  breaklines=true,
  columns=fullflexible,
  keepspaces=true,
  showstringspaces=false,
}

\NewTCBListing{plotexample}{}{%
  enhanced,
  breakable,
  listing style=numodelcode,
  colback=black!3,
  colframe=black!50,
  boxrule=0.4pt,
  arc=2pt,
  before skip=10pt,
  after skip=10pt,
}

% Match tick-label backgrounds to the plotexample colback so the
% manual's example plots blend into the listing's gray frame instead
% of the package default (fill=white).  See §"Tick label backgrounds"
% in the body for how end users adapt this to their own document.
\pgfplotsset{numodel/axis/.append style={%
  xticklabel style={fill=black!3},%
  yticklabel style={fill=black!3}%
}}

\begin{document}

\CheckSum{0}

\changes{v0.1}{2026/04/24}{Initial version, extracted from internal
  project sources.}
\changes{v0.2.0}{2026/05/16}{l3build workflow; bundle structure
  with numodel; \cs{drawplot} now invokes \cs{calcplotdims}
  internally; default axis-label-format renamed to \texttt{ieee}.}
\changes{v0.3.0}{2026/05/17}{Fix \cs{par}-token leak in
  \cs{calcplotdims} that bloated the picture bounding box inside
  horizontal boxes; fix \texttt{grid / unknown} key syntax so custom
  \texttt{grid=} values work; move \texttt{legend pos=outer north
  east} into the \texttt{numodel/axis} style so it can be overridden;
  add rendered example plots throughout the manual.}
\changes{v0.4.0}{2026/05/19}{Version-sync release with
  \textsf{numodel}~v0.4.0; no functional changes to
  \textsf{numodel-plot} itself.  Example files renamed from
  \texttt{numodel-plot-simple.tex}/\texttt{numodel-plot-scaled.tex}
  to \texttt{numodel-plot-example-basic.tex}/\texttt{numodel-plot-example-scaled.tex}.}
\changes{v0.5.0}{2026/05/23}{Version-sync release with
  \textsf{numodel}~v0.5.0; no functional changes to
  \textsf{numodel-plot} itself.}

\GetFileInfo{numodel-plot.sty}

\DoNotIndex{\newcommand,\newenvironment,\def,\edef,\let,\global,
  \RequirePackage,\usepgfplotslibrary,\pgfplotsset,\ProvidesPackage,
  \NeedsTeXFormat,\makeatletter,\makeatother,\endinput,\providecommand,
  \NewDocumentCommand,\ExplSyntaxOn,\ExplSyntaxOff,\ifnum,\ifdefined,
  \fi,\else,\relax,\undefined,\fpeval,\si,\qty,\num,\penalty,\nobreak,
  \begin,\end,\thinspace}

\title{The \textsf{numodel-plot} package\thanks{This document
  corresponds to \textsf{numodel-plot}~\fileversion, dated \today.}}
\author{Paul Zuurbier \\ \texttt{mail@paulzuurbier.nl}}
\date{\today}
\maketitle

\begin{abstract}
A PGFPlots engine that auto-sizes plots to a whole number of tick
intervals, supports configurable axis-label formats (IEEE-style
by default; ISO 80000-1 also supported), and automatically selects
label placement for 1-, 2-, and 4-quadrant graphs.  Part of the \textsf{numodel} package suite, but
can be loaded standalone as an independent PGFPlots styling layer.
\end{abstract}

\tableofcontents

\section{Introduction}

\textsf{numodel-plot} fills a gap between bare PGFPlots and the
heavy styling required for physics-teaching material: it sizes every
axis to an integer number of centimetre ticks, lays out the axis
origin according to which quadrants of the coordinate plane contain
data, and renders axis labels as either |quantity (unit)| (IEEE,
the default) or one of four alternative conventions selectable at
package level.  It was extracted from a Dutch high-school physics
test set where uniform plot appearance across hundreds of graphs is
more valuable than per-graph tweaking, and hence adopts an
opinionated default style.  Users who need one-off deviations are
expected to drop to plain PGFPlots with the variable macros
|\xmin|, |\xmax|, \ldots{} exposed by this package.

\newpage
\section{Usage}

Minimum working example (assuming |\usepackage{numodel-plot}| in
the preamble):

\begin{plotexample}
\def\xmin{-5}   \def\xmax{10}
\def\ymin{-3}   \def\ymax{5}
\def\xlabelqty{t}  \def\xlabelunit{\s}
\def\ylabelqty{v}  \def\ylabelunit{\m\per\s}
\drawplot{\addplot[domain=\xmin:\xmax]{0.5*x};}
\end{plotexample}

The user sets the data range (|\xmin|\ldots|\ymax|) and optionally a
quantity symbol plus \textsf{siunitx} unit for each axis.
|\drawplot| internally calls |\calcplotdims| to round the range to a
clean tick lattice and compute the axis size in centimetres, then
renders a full |tikzpicture|+|axis| environment whose body is the
argument (one or more |\addplot| lines).

Labels are built automatically from |\xlabelqty|+|\xlabelunit| (and
likewise for the $y$-axis).  If the data magnitude exceeds $10^{4}$
or is below $10^{-2}$, a factor~$10^{n}$ is injected into the label
and PGFPlots' own |scaled ticks| are configured so that tick numbers
remain small.  In the next example the data magnitude is
$5\times10^{7}$ and the unit |\mega\joule\per\kilo\gram| already
carries two engineering prefixes; the package extracts every prefix
and combines them with the magnitude into a single power-of-ten
factor:
\begin{plotexample}
\def\xmin{0}   \def\xmax{10}
\def\ymin{0}   \def\ymax{5e7}
\def\xlabelqty{m}  \def\xlabelunit{\kilo\gram}
\def\ylabelqty{E}  \def\ylabelunit{\mega\joule\per\kilo\gram}
\drawplot{\addplot[domain=\xmin:\xmax]{5e6*x};}
\end{plotexample}

Users preferring full control can omit |\xlabelqty|/|\xlabelunit|
and set |\xlabel|/|\ylabel| directly; the package will use them
verbatim.

\section{Configuration}

\DescribeMacro{\numodelplotsetup}
Configuration is set through a single key--value interface:
\begin{quote}
\begin{verbatim}
\numodelplotsetup{axis-label-format=ieee, grid=mm-dots}
\end{verbatim}
\end{quote}

\subsection{Keys}

\begin{description}
\item[\texttt{axis-label-format}] Default |ieee|.  Determines the
  notation emitted for axis labels built from |\xlabelqty| and
  |\xlabelunit|:
  \begin{center}
  \begin{tabular}{lll}
    \texttt{ieee}      & \verb|v (m/s)|      & IEEE (default) \\
    \texttt{iso}       & \verb|v / (m/s)|    & ISO 80000-1 \\
    \texttt{brackets}  & \verb|v [m/s]|      & older physics convention \\
    \texttt{qty-only}  & \verb|v|            & quantity symbol only \\
    \texttt{unit-only} & \verb|m/s|          & unit only \\
  \end{tabular}
  \end{center}
  When scaling is applied (data exceeds $10^{4}$ or below
  $10^{-2}$), the factor is integrated into the label, e.g.\
  \verb|v (10^4 m/s)| for IEEE.  Under |qty-only| the exponent
  remains in PGFPlots' scaled-tick label instead (otherwise the
  scale information would be lost).
\item[\texttt{grid}] Default |mm-dots| (black millimetre dots,
  matching engineering millimetre paper).  Accepts |none|, or any
  PGFPlots style list which will be passed verbatim to the
  |numodel/grid| style.
\item[\texttt{xcmmax}, \texttt{ycmmax}] Maximum axis width and
  height in centimetres (defaults 12 and 10).
\end{description}

The first three axis-label formats render as follows.  Each plot
uses |\numodelplotsetup{xcmmax=3, ycmmax=3}| so the axis itself is
trimmed to a 2~cm by 3~cm tick lattice (the package's invariant 1~cm
major-grid spacing is preserved):

\begin{plotexample}
\captionsetup{type=figure}%
\begin{subfigure}{0.33\textwidth}\centering
\numodelplotsetup{xcmmax=3, ycmmax=3, axis-label-format=ieee}%
\def\xmin{0}\def\xmax{10}\def\ymin{0}\def\ymax{5}%
\def\xlabelqty{v}\def\xlabelunit{\m\per\s}%
\def\ylabelqty{F}\def\ylabelunit{\N}%
\drawplot{\addplot[domain=\xmin:\xmax,thick]{0.4*x};}
\caption*{\texttt{ieee}}
\end{subfigure}\hspace{-1cm}%
\begin{subfigure}{0.33\textwidth}\centering
\numodelplotsetup{xcmmax=3, ycmmax=3, axis-label-format=iso}%
\def\xmin{0}\def\xmax{10}\def\ymin{0}\def\ymax{5}%
\def\xlabelqty{v}\def\xlabelunit{\m\per\s}%
\def\ylabelqty{F}\def\ylabelunit{\N}%
\drawplot{\addplot[domain=\xmin:\xmax,thick]{0.4*x};}
\caption*{\texttt{iso}}
\end{subfigure}\hspace{-1cm}%
\begin{subfigure}{0.33\textwidth}\centering
\numodelplotsetup{xcmmax=3, ycmmax=3, axis-label-format=brackets}%
\def\xmin{0}\def\xmax{10}\def\ymin{0}\def\ymax{5}%
\def\xlabelqty{v}\def\xlabelunit{\m\per\s}%
\def\ylabelqty{F}\def\ylabelunit{\N}%
\drawplot{\addplot[domain=\xmin:\xmax,thick]{0.4*x};}
\caption*{\texttt{brackets}}
\end{subfigure}
\end{plotexample}
\numodelplotsetup{axis-label-format=ieee}%

Three grid variants, sized the same way:

\begin{plotexample}
\captionsetup{type=figure}%
\begin{subfigure}{0.33\textwidth}\centering
\numodelplotsetup{xcmmax=3, ycmmax=3, grid=mm-dots}%
\def\xmin{0}\def\xmax{10}\def\ymin{0}\def\ymax{5}%
\def\xlabelqty{t}\def\xlabelunit{\s}%
\def\ylabelqty{v}\def\ylabelunit{\m\per\s}%
\drawplot{\addplot[domain=\xmin:\xmax,thick]{0.5*x};}
\caption*{\texttt{mm-dots}}
\end{subfigure}\hspace{-1cm}%
\begin{subfigure}{0.33\textwidth}\centering
\numodelplotsetup{xcmmax=3, ycmmax=3, grid=none}%
\def\xmin{0}\def\xmax{10}\def\ymin{0}\def\ymax{5}%
\def\xlabelqty{t}\def\xlabelunit{\s}%
\def\ylabelqty{v}\def\ylabelunit{\m\per\s}%
\drawplot{\addplot[domain=\xmin:\xmax,thick]{0.5*x};}
\caption*{\texttt{none}}
\end{subfigure}\hspace{-1cm}%
\begin{subfigure}{0.33\textwidth}\centering
\numodelplotsetup{xcmmax=3, ycmmax=3,
  grid={grid=major, grid style={gray, very thin}}}%
\def\xmin{0}\def\xmax{10}\def\ymin{0}\def\ymax{5}%
\def\xlabelqty{t}\def\xlabelunit{\s}%
\def\ylabelqty{v}\def\ylabelunit{\m\per\s}%
\drawplot{\addplot[domain=\xmin:\xmax,thick]{0.5*x};}
\caption*{\texttt{\{major,gray\}}}
\end{subfigure}
\end{plotexample}
\numodelplotsetup{grid=mm-dots}%

\subsection{PGFPlots styles}

The package defines three PGFPlots styles applied by |\drawplot|:
|numodel/grid|, |numodel/ticks|, |numodel/axis|.  These can be
overridden wholesale through |\pgfplotsset{numodel/axis/.style={...}}|
from the calling preamble, giving projects a single choke point for
house-style customisation.  One override per style, on the same
plot:

\begin{plotexample}
\captionsetup{type=figure}%
\begin{subfigure}{0.33\textwidth}\centering
\pgfplotsset{numodel/grid/.style={grid=major,
  grid style={gray!50, very thin}}}%
\numodelplotsetup{xcmmax=3, ycmmax=3}%
\def\xmin{0}\def\xmax{10}\def\ymin{0}\def\ymax{5}%
\def\xlabelqty{t}\def\xlabelunit{\s}%
\def\ylabelqty{v}\def\ylabelunit{\m\per\s}%
\drawplot{\addplot[domain=\xmin:\xmax,thick]{0.5*x};}
\caption*{\texttt{numodel/grid}}
\end{subfigure}\hspace{-1cm}%
\begin{subfigure}{0.33\textwidth}\centering
\pgfplotsset{numodel/ticks/.style={tick style={red, thick},
  minor tick num=4}}%
\numodelplotsetup{xcmmax=3, ycmmax=3}%
\def\xmin{0}\def\xmax{10}\def\ymin{0}\def\ymax{5}%
\def\xlabelqty{t}\def\xlabelunit{\s}%
\def\ylabelqty{v}\def\ylabelunit{\m\per\s}%
\drawplot{\addplot[domain=\xmin:\xmax,thick]{0.5*x};}
\caption*{\texttt{numodel/ticks}}
\end{subfigure}\hspace{-1cm}%
\begin{subfigure}{0.33\textwidth}\centering
\pgfplotsset{numodel/axis/.append style={axis line style={->}}}%
\numodelplotsetup{xcmmax=3, ycmmax=3}%
\def\xmin{0}\def\xmax{10}\def\ymin{0}\def\ymax{5}%
\def\xlabelqty{t}\def\xlabelunit{\s}%
\def\ylabelqty{v}\def\ylabelunit{\m\per\s}%
\drawplot{\addplot[domain=\xmin:\xmax,thick]{0.5*x};}
\caption*{\texttt{numodel/axis}}
\end{subfigure}
\end{plotexample}

\subsection{Tick label halos}

When |\calcplotdims| places an axis \emph{through the middle} of the
plot (the data straddles zero), grid lines and plotted curves pass
behind the scale numbers.  To keep those numbers readable, every
tick label on such an axis is backed by a semi-transparent halo that
follows the character outlines: the label is printed twice, first
stroke-only --- a fat pen (|1.6pt|, round joins, 80\,\% opacity, via
the \textsf{pdfrender} package) traces the glyph outlines --- and
then the normal label on top.  Between and around the digits the
grid and the curves run through untouched; there is no rectangular
patch.  So that the halo covers the curves and not only the grid,
the haloed labels are lifted onto the pgfplots layer
|axis descriptions|, above the |main| layer carrying the curves
(|set layers| is activated automatically in these cases).

An axis placed on the plot edge (the default bottom/left, or the
|top|/|right| placements used for single-sign data) leaves its
labels on the white margin and gets no halo.  Tick labels also stay
visible where a crossing axis would otherwise hide them
(|hide obscured x ticks=false| and the $y$-counterpart); the halo
provides the visual separation from the crossing axis line.

The halo colour is the named colour |numodelhalo|, white by default.
If the surrounding document has a coloured background --- a sidebar
box, a slide template, the |plotexample| environment used in this
manual --- match the halo to the surrounding colour with
\begin{quote}
\begin{verbatim}
\colorlet{numodelhalo}{<your-color>}
\end{verbatim}
\end{quote}
The mechanism itself lives in the styles |numodel/xticklabel halo|
and |numodel/yticklabel halo|; redefining them to be empty
(|\pgfplotsset{numodel/xticklabel halo/.style={}}| and the
$y$-counterpart) removes the halos entirely.

\section{Public API}

\DescribeMacro{\drawplot}
Renders a |tikzpicture| containing an |axis| whose body is the
single mandatory argument.  Typically a block of |\addplot| and
|\addlegendentry| lines.  Calls |\calcplotdims| internally, so the
user does not need to invoke it separately.

\DescribeMacro{\calcplotdims}
Reads |\xmin|, |\xmax|, |\ymin|, |\ymax|, and (if set)
|\xlabelqty|/|\xlabelunit|/|\ylabelqty|/|\ylabelunit|.  Writes
|\xcm|, |\ycm|, |\xtickdistance|, |\ytickdistance|, |\xlabel|,
|\ylabel|, and may rewrite |\xmin|\ldots|\ymax| to align with the
tick lattice (floor/ceil to the nearest tick).  It also appends
axis-positioning styles to |numodel/axis| based on which quadrants
the data occupies.  |\drawplot| invokes it automatically; expose for
advanced cases where dimensions are needed before rendering (overlay
TikZ, custom |axis| environment).

\DescribeMacro{\xlabelqty}\DescribeMacro{\xlabelunit}
\DescribeMacro{\ylabelqty}\DescribeMacro{\ylabelunit}
Input hooks for automatic label construction.  |\xlabelqty| is the
mathematical quantity symbol (e.g.\ |v|, |F|, |E|); the corresponding
|\xlabelunit| is a bare \textsf{siunitx} unit macro sequence
(e.g.\ |\m\per\s|, |\J|, |\N\m|) \emph{without} a surrounding |\si{}|
or |\qty{}| wrapper.

\DescribeMacro{\xcmmax}\DescribeMacro{\ycmmax}
Maximum axis dimensions in centimetres.  Can be set directly through
|\def| for backwards compatibility, or via |\numodelplotsetup|.

\DescribeMacro{\qtyPlain}
Like \textsf{siunitx}'s |\qty| but prints no numeric mantissa when
the final output after prefix extraction has mantissa~1 and
exponent~0.  Used internally to inject scale factors into axis
labels; exposed because the same need recurs in other scaled-axis
contexts.

\DescribeMacro{\pzuIfUnitNonEngTF}
Boolean conditional testing whether a unit macro sequence contains a
non-engineering SI prefix (|\centi|, |\deci|, |\deca|, |\hecto|,
plus the \textsf{siunitx} short forms |\cm|, |\dm|, |\hPa|, \ldots).
Used internally to suppress scaling on units where the user has
already encoded the order of magnitude; exposed for completeness.

\section{Requirements}

\textsf{numodel-plot} requires \textsf{expl3}, \textsf{xparse},
\textsf{l3keys2e}, \textsf{siunitx} (mandatory, for quantities in
labels), and \textsf{pgfplots} (with the |fillbetween| library).
LuaLaTeX is not required at the plot layer (it is required by the
sibling \textsf{numodel} package).

\end{document}
