% numodel-plot.dtx % % Docstrip source for numodel-plot.sty. % Run % tex numodel-plot.ins % to extract the derived file. User-facing documentation lives % in numodel-plot-manual.tex (a stand-alone LaTeX file). % % Copyright (C) 2026 Paul Zuurbier % % This work may be distributed and/or modified under the conditions % of the LaTeX Project Public License, either version 1.3c of this % license or (at your option) any later version. The latest version % of this license is in https://www.latex-project.org/lppl.txt % % This work has the LPPL maintenance status 'maintained'. % The Current Maintainer of this work is Paul Zuurbier. % % This work consists of the files numodel-plot.dtx and numodel-plot.ins, % and the derived file numodel-plot.sty. % \section{Implementation} % % All internal helper macros use the \texttt{\textbackslash nmp@\ldots} % prefix (standard LaTeX internal convention). Because the package % may be loaded through |\input| as well as |\usepackage|, an % explicit |\makeatletter| wraps the body so that |@| is a letter % regardless of caller. % % \begin{macrocode} \NeedsTeXFormat{LaTeX2e} \ProvidesPackage{numodel-plot}[2026/05/26 v0.6.0 Auto-sizing PGFPlots engine] \makeatletter \RequirePackage{expl3} \RequirePackage{xparse} \RequirePackage{l3keys2e} \RequirePackage{siunitx} \RequirePackage{pgfplots} \usepgfplotslibrary{fillbetween} \pgfplotsset{ compat=1.18, numodel/grid/.style={ grid=both, grid style={ line width=0.5pt, draw=black, line cap=round, dash pattern=on 0pt off 1mm }, major grid style={ line width=0.5pt, draw=black, line cap=round, dash pattern=on 0pt off 1mm} }, numodel/ticks/.style={ tick style={black} }, numodel/axis/.style={ xticklabel style={/pgf/number format/.cd, fixed, precision=6, fixed zerofill=false, use comma, 1000 sep={\,}, /tikz/.cd, text depth=0.3ex}, yticklabel style={/pgf/number format/.cd, fixed, precision=6, fixed zerofill=false, use comma, 1000 sep={\,}, /tikz/.cd, text depth=0.3ex}, axis lines=left, xlabel near ticks, ylabel near ticks, ylabel style={rotate=-90}, axis line style={-} } } \ExplSyntaxOn \tl_new:N \g__numodelplot_axislabel_tl \tl_gset:Nn \g__numodelplot_axislabel_tl {ieee} \providecommand{\xcmmax}{12} \providecommand{\ycmmax}{10} \keys_define:nn { numodel-plot } { axis-label-format .choice:, axis-label-format / iso .code:n = \tl_gset:Nn \g__numodelplot_axislabel_tl {iso}, axis-label-format / ieee .code:n = \tl_gset:Nn \g__numodelplot_axislabel_tl {ieee}, axis-label-format / brackets .code:n = \tl_gset:Nn \g__numodelplot_axislabel_tl {brackets}, axis-label-format / qty-only .code:n = \tl_gset:Nn \g__numodelplot_axislabel_tl {qty-only}, axis-label-format / unit-only .code:n = \tl_gset:Nn \g__numodelplot_axislabel_tl {unit-only}, grid .choice:, grid / mm-dots .code:n = \pgfplotsset{ numodel/grid/.style={ grid=both, grid~style={line~width=0.5pt, draw=black, line~cap=round, dash~pattern=on~0pt~off~1mm}, major~grid~style={line~width=0.5pt, draw=black, line~cap=round, dash~pattern=on~0pt~off~1mm} } }, grid / none .code:n = \pgfplotsset{ numodel/grid/.style={grid=none} }, grid / unknown .code:n = \pgfplotsset{ numodel/grid/.style={#1} }, xcmmax .code:n = \def\xcmmax{#1}, ycmmax .code:n = \def\ycmmax{#1}, } \NewDocumentCommand{\numodelplotsetup}{m} { \keys_set:nn { numodel-plot } {#1} } \ExplSyntaxOff \ExplSyntaxOn \bool_new:N \g__pzu_suppress_qp_bool \cs_new_eq:NN \__pzu_orig_int_output:nnn \__siunitx_number_output_integer:nnn \cs_new_eq:NN \__pzu_orig_qty_print_unit:n \__siunitx_quantity_print_unit:n \NewDocumentCommand{\qtyPlain}{O{} m m}{% \group_begin: \bool_gset_false:N \g__pzu_suppress_qp_bool \cs_set:Npn \__siunitx_number_output_integer:nnn ##1##2##3 { \bool_lazy_all:nTF { { \str_if_eq_p:nn { ##1 . ##2 } { 1. } } { \str_if_eq_p:nn {##3} { 0 } } { ! \l__siunitx_number_zero_exponent_bool } { ! \l__siunitx_number_unity_mantissa_bool } } { \bool_gset_true:N \g__pzu_suppress_qp_bool } { \__pzu_orig_int_output:nnn {##1} {##2} {##3} } } \cs_set_protected:Npn \__siunitx_quantity_print_unit:n ##1 { \bool_if:NTF \l__siunitx_quantity_break_bool { \penalty \binoppenalty } { \nobreak } \bool_if:NF \g__pzu_suppress_qp_bool { \tl_use:N \l__siunitx_quantity_product_tl } \siunitx_print_unit:n {##1} } \qty[#1]{#2}{#3}% \group_end: } \ExplSyntaxOff \ExplSyntaxOn \bool_new:N \l__pzu_noneng_bool \tl_const:Nn \c__pzu_noneng_tokens_tl { \centi \deci \deca \hecto \cm \cg \cL \dm \dg \dL \dam \dag \daL \hm \hg \hL \hPa } \prg_new_protected_conditional:Npnn \__pzu_unit_has_noneng:n #1 { TF } { \bool_set_false:N \l__pzu_noneng_bool \tl_map_inline:Nn \c__pzu_noneng_tokens_tl { \tl_if_in:nnT {#1} {##1} { \bool_set_true:N \l__pzu_noneng_bool \tl_map_break: } } \bool_if:NTF \l__pzu_noneng_bool { \prg_return_true: } { \prg_return_false: } } \prg_generate_conditional_variant:Nnn \__pzu_unit_has_noneng:n { o } { TF } \NewDocumentCommand{\pzuIfUnitNonEngTF}{m m m}{% \__pzu_unit_has_noneng:oTF {#1} {#2} {#3}% } \ExplSyntaxOff \ExplSyntaxOn \cs_new_protected:Npn \__numodelplot_xlabel_emit_unscaled: { \str_case:Vn \g__numodelplot_axislabel_tl { {iso} { \def \xlabel { $\nmp@savedxlabelqty \, /\, ( \si{\nmp@savedxlabelunit} )$ } } {ieee} { \def \xlabel { $\nmp@savedxlabelqty \,( \si{\nmp@savedxlabelunit} )$ } } {brackets} { \def \xlabel { $\nmp@savedxlabelqty \,[ \si{\nmp@savedxlabelunit} ]$ } } {qty-only} { \def \xlabel { $\nmp@savedxlabelqty$ } } {unit-only} { \def \xlabel { $\si{\nmp@savedxlabelunit}$ } } } } \cs_new_protected:Npn \__numodelplot_xlabel_emit_scaled: { \str_case:Vn \g__numodelplot_axislabel_tl { {iso} { \edef\xlabel{\noexpand$\noexpand\nmp@savedxlabelqty \noexpand\,/\noexpand\,(\noexpand\qtyPlain [evaluate-expression=false, round-mode=none, prefix-mode=extract-exponent, extract-mass-in-kilograms=true, print-zero-exponent=false, print-unity-mantissa=false]% {1e\nmp@xlabelexpuse}{\noexpand\nmp@savedxlabelunit})\noexpand$}% } {ieee} { \edef\xlabel{\noexpand$\noexpand\nmp@savedxlabelqty \noexpand\,(\noexpand\qtyPlain [evaluate-expression=false, round-mode=none, prefix-mode=extract-exponent, extract-mass-in-kilograms=true, print-zero-exponent=false, print-unity-mantissa=false]% {1e\nmp@xlabelexpuse}{\noexpand\nmp@savedxlabelunit})\noexpand$}% } {brackets} { \edef\xlabel{\noexpand$\noexpand\nmp@savedxlabelqty \noexpand\,[\noexpand\qtyPlain [evaluate-expression=false, round-mode=none, prefix-mode=extract-exponent, extract-mass-in-kilograms=true, print-zero-exponent=false, print-unity-mantissa=false]% {1e\nmp@xlabelexpuse}{\noexpand\nmp@savedxlabelunit}]\noexpand$}% } {qty-only} { \def \xlabel { $\nmp@savedxlabelqty$ } } {unit-only} { \edef\xlabel{\noexpand$\noexpand\qtyPlain [evaluate-expression=false, round-mode=none, prefix-mode=extract-exponent, extract-mass-in-kilograms=true, print-zero-exponent=false, print-unity-mantissa=false]% {1e\nmp@xlabelexpuse}{\noexpand\nmp@savedxlabelunit}\noexpand$}% } } } \cs_new_protected:Npn \__numodelplot_ylabel_emit_unscaled: { \str_case:Vn \g__numodelplot_axislabel_tl { {iso} { \def \ylabel { $\nmp@savedylabelqty \, /\, ( \si{\nmp@savedylabelunit} )$ } } {ieee} { \def \ylabel { $\nmp@savedylabelqty \,( \si{\nmp@savedylabelunit} )$ } } {brackets} { \def \ylabel { $\nmp@savedylabelqty \,[ \si{\nmp@savedylabelunit} ]$ } } {qty-only} { \def \ylabel { $\nmp@savedylabelqty$ } } {unit-only} { \def \ylabel { $\si{\nmp@savedylabelunit}$ } } } } \cs_new_protected:Npn \__numodelplot_ylabel_emit_scaled: { \str_case:Vn \g__numodelplot_axislabel_tl { {iso} { \edef\ylabel{\noexpand$\noexpand\nmp@savedylabelqty \noexpand\,/\noexpand\,(\noexpand\qtyPlain [evaluate-expression=false, round-mode=none, prefix-mode=extract-exponent, extract-mass-in-kilograms=true, print-zero-exponent=false, print-unity-mantissa=false]% {1e\nmp@ylabelexpuse}{\noexpand\nmp@savedylabelunit})\noexpand$}% } {ieee} { \edef\ylabel{\noexpand$\noexpand\nmp@savedylabelqty \noexpand\,(\noexpand\qtyPlain [evaluate-expression=false, round-mode=none, prefix-mode=extract-exponent, extract-mass-in-kilograms=true, print-zero-exponent=false, print-unity-mantissa=false]% {1e\nmp@ylabelexpuse}{\noexpand\nmp@savedylabelunit})\noexpand$}% } {brackets} { \edef\ylabel{\noexpand$\noexpand\nmp@savedylabelqty \noexpand\,[\noexpand\qtyPlain [evaluate-expression=false, round-mode=none, prefix-mode=extract-exponent, extract-mass-in-kilograms=true, print-zero-exponent=false, print-unity-mantissa=false]% {1e\nmp@ylabelexpuse}{\noexpand\nmp@savedylabelunit}]\noexpand$}% } {qty-only} { \def \ylabel { $\nmp@savedylabelqty$ } } {unit-only} { \edef\ylabel{\noexpand$\noexpand\qtyPlain [evaluate-expression=false, round-mode=none, prefix-mode=extract-exponent, extract-mass-in-kilograms=true, print-zero-exponent=false, print-unity-mantissa=false]% {1e\nmp@ylabelexpuse}{\noexpand\nmp@savedylabelunit}\noexpand$}% } } } \ExplSyntaxOff \newcommand{\pznmpAppendXScaleTicks}[1]{% \pgfplotsset{numodel/axis/.append style={scaled x ticks=base 10:#1}}% } \newcommand{\pznmpAppendYScaleTicks}[1]{% \pgfplotsset{numodel/axis/.append style={scaled y ticks=base 10:#1}}% } \newcommand{\pznmpSuppressXScaleLabel}{% \pgfplotsset{numodel/axis/.append style={every x tick scale label/.style={opacity=0, inner sep=0pt, at={(0,0)}}}}% } \newcommand{\pznmpSuppressYScaleLabel}{% \pgfplotsset{numodel/axis/.append style={every y tick scale label/.style={opacity=0, inner sep=0pt, at={(0,0)}}}}% } \ExplSyntaxOn \cs_new_protected:Npn \__numodelplot_suppress_xscale_label: { \str_if_eq:VnF \g__numodelplot_axislabel_tl {qty-only} { \pznmpSuppressXScaleLabel } } \cs_new_protected:Npn \__numodelplot_suppress_yscale_label: { \str_if_eq:VnF \g__numodelplot_axislabel_tl {qty-only} { \pznmpSuppressYScaleLabel } } \ExplSyntaxOff \ExplSyntaxOn \NewDocumentCommand{\xlabelbuild}{}{ \let\nmp@savedxlabelqty\xlabelqty \let\nmp@savedxlabelunit\xlabelunit \pzuIfUnitNonEngTF{\xlabelunit}{ \__numodelplot_xlabel_emit_unscaled: \def\nmp@xlabelexp{0} }{ \edef\nmp@xmag{\fpeval{max(abs(\xmin),abs(\xmax))}} \edef\nmp@xlabelexp{\fpeval{ \nmp@xmag > 0 ? 3*floor(ln(\nmp@xmag)/ln(10)/3) : 0 }} \ifnum\nmp@xlabelexp=0\relax \__numodelplot_xlabel_emit_unscaled: \else \edef\nmp@xlabelexpuse{\nmp@xlabelexp} \__numodelplot_xlabel_emit_scaled: \edef\nmp@xlabelexpneg{\fpeval{-\nmp@xlabelexp}} \pznmpAppendXScaleTicks{\nmp@xlabelexpneg} \__numodelplot_suppress_xscale_label: \fi } \global\let\xlabelqty\undefined \global\let\xlabelunit\undefined } \NewDocumentCommand{\ylabelbuild}{}{ \let\nmp@savedylabelqty\ylabelqty \let\nmp@savedylabelunit\ylabelunit \pzuIfUnitNonEngTF{\ylabelunit}{ \__numodelplot_ylabel_emit_unscaled: \def\nmp@ylabelexp{0} }{ \edef\nmp@ymag{\fpeval{max(abs(\ymin),abs(\ymax))}} \edef\nmp@ylabelexp{\fpeval{ \nmp@ymag > 0 ? 3*floor(ln(\nmp@ymag)/ln(10)/3) : 0 }} \ifnum\nmp@ylabelexp=0\relax \__numodelplot_ylabel_emit_unscaled: \else \edef\nmp@ylabelexpuse{\nmp@ylabelexp} \__numodelplot_ylabel_emit_scaled: \edef\nmp@ylabelexpneg{\fpeval{-\nmp@ylabelexp}} \pznmpAppendYScaleTicks{\nmp@ylabelexpneg} \__numodelplot_suppress_yscale_label: \fi } \global\let\ylabelqty\undefined \global\let\ylabelunit\undefined } \ExplSyntaxOff \newcommand{\calcplotdims}{% \edef\nmp@xlog{\fpeval{floor(ln(\xmax-\xmin)/ln(10))}}% \edef\nmp@ylog{\fpeval{floor(ln(\ymax-\ymin)/ln(10))}}% \edef\nmp@xS{\fpeval{(\xmax-\xmin) / (10^\nmp@xlog)}}% \edef\nmp@yS{\fpeval{(\ymax-\ymin) / (10^\nmp@ylog)}}% \edef\xcm{\fpeval{% \nmp@xS <= \xcmmax/10 ? 10 * \nmp@xS :% \nmp@xS <= \xcmmax/5 ? 5 * \nmp@xS :% \nmp@xS <= \xcmmax/4 ? 4 * \nmp@xS :% \nmp@xS <= \xcmmax/2 ? 2 * \nmp@xS :% \nmp@xS <= \xcmmax ? \nmp@xS :% \nmp@xS / 2% }}% \edef\ycm{\fpeval{% \nmp@yS <= \ycmmax/10 ? 10 * \nmp@yS :% \nmp@yS <= \ycmmax/5 ? 5 * \nmp@yS :% \nmp@yS <= \ycmmax/4 ? 4 * \nmp@yS :% \nmp@yS <= \ycmmax/2 ? 2 * \nmp@yS :% \nmp@yS <= \ycmmax ? \nmp@yS :% \nmp@yS / 2% }}% \edef\nmp@xtickdiv{\fpeval{\nmp@xS / \xcm}}% \edef\nmp@ytickdiv{\fpeval{\nmp@yS / \ycm}}% \edef\xtickdistance{\fpeval{\nmp@xtickdiv * 10^\nmp@xlog}}% \edef\ytickdistance{\fpeval{\nmp@ytickdiv * 10^\nmp@ylog}}% \edef\xmax{\fpeval{ceil(\xmax / \xtickdistance) * \xtickdistance}}% \edef\ymax{\fpeval{ceil(\ymax / \ytickdistance) * \ytickdistance}}% \edef\xmin{\fpeval{floor(\xmin / \xtickdistance) * \xtickdistance}}% \edef\ymin{\fpeval{floor(\ymin / \ytickdistance) * \ytickdistance}}% \edef\xcm{\fpeval{(\xmax - \xmin) / \xtickdistance}}% \edef\ycm{\fpeval{(\ymax - \ymin) / \ytickdistance}}% \edef\nmp@yStraddles{\fpeval{(\ymin<0 && \ymax>0) ? 1 : 0}}% \edef\nmp@yAllNonPos{\fpeval{\ymax<=0 ? 1 : 0}}% \edef\nmp@xStraddles{\fpeval{(\xmin<0 && \xmax>0) ? 1 : 0}}% \edef\nmp@xAllNonPos{\fpeval{\xmax<=0 ? 1 : 0}}% \ifnum\nmp@yStraddles=1 \pgfplotsset{numodel/axis/.append style={axis x line*=middle}}% \else \ifnum\nmp@yAllNonPos=1 \pgfplotsset{numodel/axis/.append style={axis x line*=top}}% \fi \fi \ifnum\nmp@xStraddles=1 \pgfplotsset{numodel/axis/.append style={axis y line*=middle}}% \else \ifnum\nmp@xAllNonPos=1 \pgfplotsset{numodel/axis/.append style={axis y line*=right}}% \fi \fi \edef\nmp@axisMoved{\fpeval{(\nmp@yStraddles || \nmp@yAllNonPos || \nmp@xStraddles || \nmp@xAllNonPos) ? 1 : 0}}% \ifnum\nmp@axisMoved=1 \pgfplotsset{numodel/axis/.append style={% every x tick scale label/.style={at={(xticklabel cs:1.05)}, anchor=south west},% every y tick scale label/.style={at={(yticklabel cs:1.05)}, anchor=south east}% }}% \fi \edef\nmp@fourQuad{\fpeval{(\nmp@xStraddles && \nmp@yStraddles) ? 1 : 0}}% \ifnum\nmp@fourQuad=1 \pgfplotsset{numodel/axis/.append style={% xlabel style={at={(xticklabel cs:1.05)}, anchor=west},% ylabel style={at={(yticklabel cs:1.05)}, anchor=south, rotate=0}% }}% \fi \edef\nmp@qIandII{\fpeval{(\nmp@xStraddles && \ymin>=0) ? 1 : 0}}% \ifnum\nmp@qIandII=1 \pgfplotsset{numodel/axis/.append style={% ylabel style={at={(yticklabel cs:1.05)}, anchor=south, rotate=0}% }}% \fi \edef\nmp@qIIandIII{\fpeval{(\nmp@yStraddles && \nmp@xAllNonPos) ? 1 : 0}}% \ifnum\nmp@qIIandIII=1 \pgfplotsset{numodel/axis/.append style={% xlabel style={at={(xticklabel cs:-0.05)}, anchor=east}% }}% \fi \edef\nmp@qIIIandIV{\fpeval{(\nmp@xStraddles && \nmp@yAllNonPos) ? 1 : 0}}% \ifnum\nmp@qIIIandIV=1 \pgfplotsset{numodel/axis/.append style={% ylabel style={at={(yticklabel cs:-0.05)}, anchor=north, rotate=0}% }}% \fi \edef\nmp@qIVandI{\fpeval{(\nmp@yStraddles && \xmin>=0) ? 1 : 0}}% \ifnum\nmp@qIVandI=1 \pgfplotsset{numodel/axis/.append style={% xlabel style={at={(xticklabel cs:1.05)}, anchor=west}% }}% \fi \ifdefined\xlabelqty\ifdefined\xlabelunit\xlabelbuild\fi\fi \ifdefined\ylabelqty\ifdefined\ylabelunit\ylabelbuild\fi\fi } \newcommand{\drawplot}[1]{ \calcplotdims \begin{tikzpicture} \begin{axis}[ numodel/grid, numodel/ticks, numodel/axis, xlabel=\xlabel, ylabel=\ylabel, xmin=\xmin, xmax=\xmax, ymin=\ymin, ymax=\ymax, xtick distance=\xtickdistance, ytick distance=\ytickdistance, width=\xcm cm, height=\ycm cm, scale only axis ] #1 \end{axis} \end{tikzpicture} } \makeatother % \end{macrocode} % % \Finale \endinput