% 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 <mail@paulzuurbier.nl>
%
% 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
