\documentclass[a4paper,11pt]{article}
\usepackage[T1]{fontenc}
\usepackage[english]{babel}
\usepackage{lmodern}
\usepackage{microtype}
\usepackage{geometry}
\usepackage{xcolor}
\usepackage{graphicx}
\usepackage{booktabs}
\usepackage{tabularx}
\usepackage{hyperref}
\usepackage[noautomatic,quiet]{imakeidx}
\usepackage{listings}
\usepackage[most]{tcolorbox}
\usepackage{pgfplots-autonode}

\geometry{margin=24mm}
\hypersetup{
  colorlinks=true,
  linkcolor=blue!55!black,
  urlcolor=blue!55!black,
  pdftitle={pgfplots-autonode Reference Manual},
  pdfauthor={Christophe Jorssen},
  pdfsubject={Automatic collision-aware labels for PGFPlots curves},
  pdfkeywords={LuaTeX, PGFPlots, TikZ, labels, collision avoidance}
}
\pgfplotsset{compat=1.18}

\definecolor{ANBlue}{RGB}{28,70,140}
\definecolor{ANGreen}{RGB}{0,110,95}
\definecolor{ANOrange}{RGB}{190,95,20}
\definecolor{ANGray}{RGB}{246,247,249}
\definecolor{ANLightBlue}{RGB}{235,243,255}

\lstdefinestyle{antex}{basicstyle=\ttfamily\small,columns=fullflexible,
  keepspaces=true,breaklines=true}
\newtcblisting{ANExample}[2][]{enhanced,breakable,colback=ANGray,
  colframe=ANBlue!70!black,listing only,listing engine=listings,
  listing options={style=antex},title={#2},fonttitle=\bfseries,
  left=1mm,right=1mm,top=1mm,bottom=1mm,#1}
\newtcblisting{ANShowcase}[2][]{enhanced,colback=ANGray,
  colframe=ANBlue!70!black,listing side text,sidebyside align=top seam,
  sidebyside gap=4mm,lefthand ratio=.56,listing engine=listings,
  listing options={style=antex},title={#2},fonttitle=\bfseries,
  left=1mm,right=1mm,top=1mm,bottom=1mm,#1}
\newtcblisting{ANShowcaseWide}[2][]{enhanced,breakable,colback=ANGray,
  colframe=ANBlue!70!black,listing and text,listing engine=listings,
  listing options={style=antex},title={#2},fonttitle=\bfseries,
  left=1mm,right=1mm,top=1mm,bottom=1mm,#1}
\newtcolorbox{ANNote}[1][]{enhanced,breakable,colback=ANLightBlue,
  colframe=ANBlue!70!black,title={#1},fonttitle=\bfseries}
\newtcolorbox{ANWarning}[1][]{enhanced,breakable,colback=orange!8,
  colframe=ANOrange!85!black,title={#1},fonttitle=\bfseries}

\newcommand{\pkg}[1]{\texttt{#1}}
\newcommand{\key}[1]{\texttt{#1}}
\newcommand{\macro}[1]{\texttt{\string#1}}
\newcommand{\meta}[1]{\ensuremath{\langle\textit{#1}\rangle}}
\newcommand{\marg}[1]{\texttt{\char`\{}\meta{#1}\texttt{\char`\}}}
\newcommand{\oarg}[1]{\texttt{[}\meta{#1}\texttt{]}}

\makeindex[name=commands,title={Command Index},columns=2]
\makeindex[name=keys,title={Key and Value Index},columns=2]

\newenvironment{ANCommandRef}[2]{%
  \index[commands]{#1@\protect\texttt{\protect\textbackslash#1}}%
  \begin{tcolorbox}[enhanced,breakable,colback=ANLightBlue,
    colframe=ANBlue!75!black,fonttitle=\bfseries,
    title={\texttt{\textbackslash#1}#2}]%
}{\end{tcolorbox}}
\newenvironment{ANKeyRef}[4]{%
  \index[keys]{#2@\protect\texttt{#2}}%
  \begin{tcolorbox}[enhanced,breakable,colback=white,
    colframe=ANGreen!70!black,fonttitle=\bfseries,
    title={\texttt{#2}=\meta{#3}\hfill\normalfont\small
      Default: \texttt{#4}}]%
}{\end{tcolorbox}}
\newcommand{\ANKeyAlias}[3]{%
  \index[keys]{#2@\protect\texttt{#2} (alias)}%
  \par\smallskip\noindent\textit{Accepted alias: }
  \texttt{#2} forwards to \texttt{#3}.\par}
\newcommand{\ANChoice}[4]{%
  \index[keys]{#2=#3@\protect\texttt{#2=#3}}%
  \item[\texttt{#3}] #4}
\newcommand{\ANIndexValue}[2]{%
  \index[keys]{#1=#2@\protect\texttt{#1=#2}}}
\newcommand{\ANKeyUse}[3]{%
  \par\smallskip\noindent\textit{Example: }
  \texttt{#2=\detokenize{#3}}.\par}
\newcommand{\ANCodeUse}[1]{%
  \par\smallskip\noindent\textit{Example: }
  \texttt{\detokenize{#1}}.\par}
\newcommand{\ANKeyDoc}[6]{%
  \begin{ANKeyRef}{#1}{#2}{#3}{#4}%
  #5\ANKeyUse{#1}{#2}{#6}%
  \end{ANKeyRef}}

\title{\pkg{pgfplots-autonode}\\[1mm]
  \large Collision-aware automatic labels for PGFPlots curves}
\author{Christophe Jorssen\\\texttt{christophe.jorssen@gmail.com}}
\date{Version 1.0.0 --- 2026-09-20}

\begin{document}
\maketitle
\begin{abstract}
\pkg{pgfplots-autonode} is a format-generic LuaTeX library that measures TeX
labels, samples visible PGFPlots paths in canvas coordinates, and chooses a
joint collision-aware placement at the end of each axis.  It supports linear
and logarithmic axes, sloped labels, axis-boundary constraints, priorities,
deterministic bounded algorithms, and detailed diagnostics.  It is independent
of \pkg{luacoolprop} and of any application-specific curve generator.
\end{abstract}
\tableofcontents

\section{Scope, requirements, and independence}

The library requires LuaTeX and PGFPlots 1.18 or later.  It works with
LuaLaTeX, plain LuaTeX, and ConTeXt MkIV.  Any PGFPlots curve that supports
trailing path commands can use the public \macro{\pgfplotsautonode} interface.

\begin{ANWarning}[AI-assisted ``vibe coding'']
This package has been vibe-coded with ChatGPT.  Substantial parts of its
design, implementation, documentation, and tests were generated or revised
with AI assistance.  AI-generated code can appear convincing while remaining
incorrect or incomplete.  Inspect the source and validate the resulting label
layout for the intended publication.
\end{ANWarning}

Copyright \textcopyright\ 2026 Christophe Jorssen.  The package is maintained
under the LaTeX Project Public License 1.3c or later.  The Current Maintainer is
Christophe Jorssen
(\href{mailto:christophe.jorssen@gmail.com}{\nolinkurl{christophe.jorssen@gmail.com}}).
The project repository and issue tracker are hosted at
\href{https://github.com/cjorssen/pgfplots-autonode}%
{\nolinkurl{github.com/cjorssen/pgfplots-autonode}}.

\section{Tutorial: why joint placement matters}

Consider two families of affine functions, one with slope (+1) and one with
slope (-1).  Integer vertical offsets make a regular, dense crossing network.
It is simple enough that every geometrical conflict is visible, yet it exercises
the same placement problem as contour maps, spectra, and engineering charts.

\subsection{The fixed-position failure}

If every label is placed at the midpoint of its own curve, many nodes collect
near crossings.  Each plot knows nothing about labels already chosen for other
plots, so local \texttt{node[pos=.5]} instructions cannot solve the global
assignment problem.

\begin{ANShowcaseWide}{Midpoint labels collide in an affine network}
\begin{tikzpicture}
\begin{axis}[width=11cm,height=7cm,xmin=-4,xmax=4,ymin=-4,ymax=4,
  axis equal image,grid=both]
  \foreach \b in {-2,...,2}{
    \addplot[blue!70!black,domain=-4:4,samples=2] {x+\b}
      node[pos=.5,sloped,fill=white,inner sep=1pt] {$+$};
    \addplot[red!75!black,domain=-4:4,samples=2] {-x+\b}
      node[pos=.5,sloped,fill=white,inner sep=1pt] {$-$};
  }
\end{axis}
\end{tikzpicture}
\end{ANShowcaseWide}

\subsection{Register first, solve once}

Enable \key{auto node placement} on the axis and attach one
\macro{\pgfplotsautonode} command to each plot.  Registration is cheap: final
node drawing is deferred until PGFPlots has established the axis transformation
and the solver has seen every label.

\begin{ANShowcaseWide}{The same network solved jointly}
\begin{tikzpicture}
\begin{axis}[width=11cm,height=7cm,xmin=-4,xmax=4,ymin=-4,ymax=4,
  axis equal image,grid=both,auto node placement,
  auto node algorithm=repair,auto node bbox mode=oriented,
  auto node failure mode=error,auto node candidates=81]
  \foreach \b in {-2,...,2}{
    \addplot[blue!70!black,domain=-4:4,samples=2] {x+\b}
      \pgfplotsautonode[preferred pos=.5,sloped=true]{$+$};
    \addplot[red!75!black,domain=-4:4,samples=2] {-x+\b}
      \pgfplotsautonode[preferred pos=.5,sloped=true]{$-$};
  }
\end{axis}
\end{tikzpicture}
\end{ANShowcaseWide}

The preferred midpoint remains a soft objective.  The solver may move a label
along its visible curve to satisfy harder requirements: remaining inside the
plot rectangle and avoiding other measured label boxes.

\subsection{Algorithms and bounded work}

Use \key{greedy} for a quick deterministic draft, \key{repair} as the balanced
default, \key{local-search} for additional bounded improvement, and
\key{exact-small} only for genuinely small label sets.  Candidate counts,
iteration limits, and exact-search state limits make the computational budget
explicit and reproducible.  The complete reference states the fallback and
failure semantics; no heuristic is described as proving a global optimum.

\subsection{Seeing what the solver measures}

The diagnostic overlay below exposes candidate points, rejected candidates,
and the final measured boxes.  It is intended for authoring, not final artwork.

\begin{ANShowcaseWide}{Candidate and bounding-box diagnostics}
\begin{tikzpicture}
\begin{axis}[width=11cm,height=6cm,xmin=-3,xmax=3,ymin=-3,ymax=3,
  grid=both,auto node placement,auto node candidates=13,
  auto node show candidates=true,
  auto node show rejected candidates=true,
  auto node show bounding boxes=true]
  \addplot[blue,thick,domain=-3:3,samples=2] {x+1}
    \pgfplotsautonode[preferred pos=.5,sloped=true]{$y=x+1$};
  \addplot[red,thick,domain=-3:3,samples=2] {-x+1}
    \pgfplotsautonode[preferred pos=.5,sloped=true]{$y=-x+1$};
\end{axis}
\end{tikzpicture}
\end{ANShowcaseWide}

\section{Case study: a dense thermodynamic chart}

The \pkg{luacoolprop} package is a concrete user of
\pkg{pgfplots-autonode}.  It obtains thermodynamic properties from CoolProp,
constructs PGFPlots paths, and registers the requested isoline labels through
\macro{\pgfplotsautonode}.  The placement library then sees labels from all
families together; it does not need to know what an isotherm, isentrope,
isochore, or quality curve means.

The following example is extracted from the LuaCoolProp manual.  Eleven
quality curves, seven isotherms, five isentropes, and seven isochores compete
for space near the saturation dome.  Their preferred positions alone would
put several long, sloped labels in the same region.  Joint placement measures
the actual TeX nodes, searches the visible parts of every path, and keeps the
result inside the axis rectangle.

\begin{ANExample}{LuaCoolProp client code for a dense PH diagram}
\begin{tikzpicture}
\begin{axis}[
  lcp fluid=R134a,
  xlabel={$h/{\rm kJ\,kg^{-1}}$},
  ylabel={$p/{\rm bar}$},
  ymode=log,grid=both,
  auto node placement,
  auto node algorithm=repair,
  auto node bbox mode=oriented,
  auto node failure mode=error,
  width=20cm,height=15cm]
  \LCPAddPHQuality[
    quality values={0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1},
    labels=true,sloped labels=true]
  \LCPAddPHIsotherms[
    temperature values={-20,0,20,40,60,80,100},
    labels=true,sloped labels=true]
  \LCPAddPHIsentropes[
    entropy values={1.0,1.2,1.4,1.6,1.8},
    labels=true,sloped labels=true]
  \LCPAddPHIsochores[
    specific volume values={0.001,0.002,0.005,0.01,0.02,0.05,0.1},
    labels=true,sloped labels=true]
\end{axis}
\end{tikzpicture}
\end{ANExample}

\begin{center}
  \includegraphics[width=\textwidth]{examples/ph-r134a-autonode-full-network.pdf}

  \small\itshape Dense R134a PH chart generated by LuaCoolProp.  Automatic
  labels are measured and placed jointly by pgfplots-autonode.
\end{center}

This is an application example rather than part of the affine tutorial: the
prebuilt vector figure keeps the present manual self-contained, while the
complete executable source and its thermodynamic discussion remain in the
LuaCoolProp manual.

\section{Installation and loading}

Install the CTAN package with the package manager of the TeX distribution.  A
manual TDS installation places the generic PGFPlots library below
\texttt{tex/generic/pgfplots-autonode}, the Lua module below
\texttt{tex/luatex/pgfplots-autonode}, and the optional LaTeX wrapper below
\texttt{tex/latex/pgfplots-autonode}.

In LuaLaTeX, \verb|\usepackage{pgfplots-autonode}| loads PGFPlots and the
library.  The format-neutral spelling is
\verb|\usepgfplotslibrary{autonode}| after loading the appropriate PGFPlots
frontend.  The complete examples shipped with the package cover LuaLaTeX,
plain LuaTeX, and ConTeXt MkIV.

\part{Complete reference}
\input{docs/pgfplots-autonode-reference.tex}

\part{Implementation and maintenance}

\section{Architecture}

\begin{center}
\begin{tabularx}{\textwidth}{>{\raggedright\arraybackslash}p{.42\textwidth}X}
\toprule
File & Responsibility \\
\midrule
\texttt{pgflibrarypgfplots.\allowbreak autonode.\allowbreak code.tex} & PGFPlots keys, axis lifecycle,
path sampling, label measurement, and final TikZ nodes \\
\texttt{pgfplots-autonode.lua} & candidate validation, canvas geometry,
collision costs, bounded assignment algorithms, and diagnostic emission \\
\texttt{pgfplots-autonode.sty} & optional LuaLaTeX package wrapper \\
\texttt{pgfplots-autonode.code.tex} & format-neutral direct loader for users
who already loaded a PGFPlots frontend \\
\bottomrule
\end{tabularx}
\end{center}

TeX owns all token lists and typesets each label once in a scratch picture to
obtain its actual node bounds.  Lua receives only numbers, booleans, and stable
label identifiers.  Candidate positions are sampled on the visible pieces of
the surveyed path in final canvas coordinates.  Disconnected visible pieces
remain distinct, and candidates outside the plot rectangle are rejected unless
\key{auto node allow outside=true} is explicit.

\section{Lua backend contract}

The module exposes small groups of functions:
\begin{itemize}
  \item \texttt{reset} and \texttt{configure} manage one solve;
  \item \texttt{set\_axis\_rect}, \texttt{add\_label}, and
        \texttt{set\_label\_geometry} register measured input;
  \item \texttt{add\_candidate} and
        \texttt{emit\_candidate\_positions} manage samples; and
  \item \texttt{solve} and \texttt{solve\_and\_emit} assign labels.
\end{itemize}
Public boundaries validate finite numeric values, integer limits, intervals,
and enumerated modes.  Module state is local; the TeX axis lifecycle resets it
before each independent solve.  Detailed API comments live next to the
implementation in \texttt{pgfplots-autonode.lua}.

\section{Independent verification}

The release test installs only the \pkg{pgfplots-autonode} TDS archive in a
temporary TEXMF tree.  It runs the numerical Lua suite and compiles LuaLaTeX,
plain LuaTeX, and ConTeXt fixtures while rejecting any runtime reference to
\pkg{luacoolprop}.  Additional fixtures cover missing lifecycle diagnostics,
successive axes, measured asymmetric nodes, clipped paths, disconnected visible
domains, and hard axis-border constraints.

\clearpage
\printindex[commands]
\printindex[keys]
\end{document}
