# Fill percent of path along pair of almost parallel hobby curves

I have two almost parallel hobby paths (red and blue curves) and want to fill a percent of the path between these two lines, starting from the bottom left.

I think this may require a center path from which to compute ther percent of the path and have drawn that dotted and included markings at the 30%, 60% and 100% points.

What is the recommened way to fill this path?

## Hack:

One way to hack this is to mark more points along the center path with circle, but the result is not that good and will be problamatic when the width of the curve grows. So using

\draw [gray, thin, dotted, Fill Points on Path={0.02}{0.2}{0.01}]
(C-1)
to [curve through={(C-2) (C-3) (C-4)}]
(C-5);

yields:

## Code:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{hobby}
\usetikzlibrary{decorations.markings}

\tikzset{Label Points on Path/.style n args={3}{
decoration={
markings,
mark=
between positions #1 and #2 step #3
with
{
\draw [fill=blue] (0,0) circle (2pt);
}
},
postaction=decorate,
}}
\tikzset{Fill Points on Path/.style n args={3}{%% <-- Needed for hack solution
decoration={
markings,
mark=
between positions #1 and #2 step #3
with
{
\draw [fill=cyan] (0,0) circle (7pt);% large circle
}
},
postaction=decorate,
}}

\begin{document}
\begin{tikzpicture}
\coordinate (A-1) at (0, 0); \coordinate (B-1) at (0.5, 0);
\coordinate (A-2) at (1, 1); \coordinate (B-2) at (1, 0.5);
\coordinate (A-3) at (3, 1); \coordinate (B-3) at (3, 0.5);
\coordinate (A-4) at (4, 3); \coordinate (B-4) at (4, 2.5);
\coordinate (A-5) at (7, 3); \coordinate (B-5) at (A-5);

\coordinate (C-1) at (0.25, 0);
\coordinate (C-2) at (1, 0.75);
\coordinate (C-3) at (3, 0.75);
\coordinate (C-4) at (4, 2.75);
\coordinate (C-5) at (A-5);

\draw [black, fill=yellow!15]
(A-5)
to[out=-90, in=0, distance=5.0cm]
(A-1)
to [curve through={(A-2) (A-3) (A-4)}]
(A-5);

\draw [ultra thick, red]
(A-1)
to [curve through={(A-2) (A-3) (A-4)}]
(A-5);

\draw [thin, blue] (B-1)
to [curve through={(B-2) (B-3) (B-4)}]
(B-5);

\draw [gray, thick, dotted, Label Points on Path={0.2}{1}{0.4}]
(C-1)
to [curve through={(C-2) (C-3) (C-4)}]
(C-5);

%% Hack solution
%\draw [gray, thin, dotted, Fill Points on Path={0.02}{0.2}{0.01}]
%    (C-1)
%    to [curve through={(C-2) (C-3) (C-4)}]
%    (C-5);

\end{tikzpicture}
\end{document}

## Symbol 1's Solution (Packaged Version):

This is an attempt to package Symbol 1's solution.

It seems to have issue filling in the initial portion and the end potions of the curve. The image is after drawing over the curve following the fill.

Also, I would prefer the end of the fill to be a circular-ish bulge (end of a circle as I showed in the blue fill example). If it adds much complexity to the solution, I can live without this, and atetmpt to remedy that by placing a circle of an appropriate size near the end of the fill.

Besides the glitches, I can't seem to fill beyond the 93% point (which is what is shown in the image). Similarly, below 5% has issues.

## Code:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{hobby}
\usetikzlibrary{decorations}

\def\PercentOfPath{93}

\pgfmathsetmacro\PotionOfFill{1.0 -  \PercentOfPath/100}
\pgfdeclaredecoration{CurveToDesiredPoint}{initial}{%
\state{initial}[
width=\pgfdecoratedinputsegmentlength/5,
% replace 5 by larger number to improve resolution
switch if less than=\PotionOfFill*\pgfdecoratedpathlength to final
]{
\pgfpathlineto{\pgfpointorigin}
}%
\state{final}{}%
}

\newcommand\DrawPath[1][]{%
\draw [red, thick, #1]  (A-1) to [curve through={(A-2) (A-3) (A-4)}] (A-5);
\draw [blue, thick, #1] (B-1) to [curve through={(B-2) (B-3) (B-4)}] (B-5);
}

\begin{document}
\begin{tikzpicture}
\coordinate (A-1) at (0, 0); \coordinate (B-1) at (0.5, 0);
\coordinate (A-2) at (1, 1); \coordinate (B-2) at (1, 0.5);
\coordinate (A-3) at (3, 1); \coordinate (B-3) at (3, 0.5);
\coordinate (A-4) at (4, 3); \coordinate (B-4) at (4, 2.5);
\coordinate (A-5) at (7, 3); \coordinate (B-5) at (A-5);

\coordinate (C-1) at (0.25, 0);
\coordinate (C-2) at (1, 0.75);
\coordinate (C-3) at (3, 0.75);
\coordinate (C-4) at (4, 2.75);
\coordinate (C-5) at (A-5);

\DrawPath

\tikzset{decoration={CurveToDesiredPoint}}
\DrawPath[decorate, draw=none]

\path [red, decorate, save path=\redpanda]
(A-1) to [curve through={(A-2) (A-3) (A-4)}] (A-5);
\path[use path=\redpanda, red];
\path[use path=\redpanda, red!80, transform canvas={yshift=-5}];
\path[use path=\redpanda, red!60, transform canvas={yshift=-10}];
\path[use path=\redpanda, red!40, transform canvas={yshift=-15}];
\path[use path=\redpanda, red!20, transform canvas={yshift=-20}];

\tikzset{decoration={CurveToDesiredPoint} }
\path [blue, decorate, save path=\bluewhale]
(B-1) to [curve through={(B-2) (B-3) (B-4)}] (B-5);

\makeatletter{
\def\orangeshark{}
\def\pgfsyssoftpath@linetotoken#1#2{
\xdef\orangeshark{
\orangeshark
}
}
\let\pgfsyssoftpath@movetotoken=\pgfsyssoftpath@linetotoken
\bluewhale
}

\def\zoo{\redpanda\orangeshark}
\fill[yellow]\pgfextra{\pgfsetpath\zoo};
\path \pgfextra{\pgfsetpath\redpanda};
\path \pgfextra{\pgfsetpath\bluewhale};

\DrawPath% To cover up any glitches
\end{tikzpicture}%
\end{document}
• I'm not sure I understood your question. But the flexible grid may be a solution. Flexure of a Grid – AndréC Aug 22 '20 at 5:43
• @AndréC: Thanks for the link. I am not sure how to adapt that. In the mean time, I did a hack to illustrate the desird result. Hope that clarifies things. – Peter Grill Aug 22 '20 at 6:31
• Ah, you want a 3-D effect if I understand correctly. – AndréC Aug 22 '20 at 6:35
• @AndréC: Don't need 3-d effect, just plain fill will do. – Peter Grill Aug 22 '20 at 6:42
• How automatic do you want the solution to be? One option would be to use the markings library to place a coordinate at the relevant percentage along each path, then truncate the paths at those points. A useful feature of hobby's algorithm is that adding a point that is already on the path doesn't change the result, so you could re-run the algorithm to get a path that can be easily split at the marked points. – Andrew Stacey Aug 22 '20 at 6:50

# First try

Let me know if this is not what you want.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{hobby}
\usetikzlibrary{decorations}

\begin{document}

Define points.
I am abusing the scoping of Ti\emph kZ.

\tikz{
\coordinate (A-1) at (0, 0); \coordinate (B-1) at (0.5, 0);
\coordinate (A-2) at (1, 1); \coordinate (B-2) at (1, 0.5);
\coordinate (A-3) at (3, 1); \coordinate (B-3) at (3, 0.5);
\coordinate (A-4) at (4, 3); \coordinate (B-4) at (4, 2.5);
\coordinate (A-5) at (7, 3); \coordinate (B-5) at (A-5);

\coordinate (C-1) at (0.25, 0);
\coordinate (C-2) at (1, 0.75);
\coordinate (C-3) at (3, 0.75);
\coordinate (C-4) at (4, 2.75);
\coordinate (C-5) at (A-5);

\draw [red] (A-1) to [curve through={(A-2) (A-3) (A-4)}] (A-5);
\draw [blue] (B-1) to [curve through={(B-2) (B-3) (B-4)}] (B-5);
}

First step:
Draw only a part of a given path.
For instance, I want to draw the first $61.8\%$.
(Just that I like golden ratio.)
\pgfdeclaredecoration{curveto618}{initial}{%
\state{initial}[
width=\pgfdecoratedinputsegmentlength/5,
% replace 5 by larger number to improve resolution
switch if less than=.384*\pgfdecoratedpathlength to final
]{
\pgfpathlineto{\pgfpointorigin}
}%
\state{final}{}%
}%

\tikz{
\tikzset{decoration={curveto618} }
\draw [red, decorate] (A-1) to [curve through={(A-2) (A-3) (A-4)}] (A-5);
\draw [blue, decorate] (B-1) to [curve through={(B-2) (B-3) (B-4)}] (B-5);
}

Second Step:
Smuggle the path out of the \texttt{\string\draw} command.
And prove that we can reuse the path.

\tikz{
\tikzset{decoration={curveto618} }
\draw [red, decorate, save path=\redpanda]
(A-1) to [curve through={(A-2) (A-3) (A-4)}] (A-5);
\draw[use path=\redpanda, red];
\draw[use path=\redpanda, red!80, transform canvas={yshift=-5}];
\draw[use path=\redpanda, red!60, transform canvas={yshift=-10}];
\draw[use path=\redpanda, red!40, transform canvas={yshift=-15}];
\draw[use path=\redpanda, red!20, transform canvas={yshift=-20}];
}

Third Step:
Invert the blue path.
(Not visible, but important.)

\tikz{
\tikzset{decoration={curveto618} }
\draw [blue, decorate, save path=\bluewhale]
(B-1) to [curve through={(B-2) (B-3) (B-4)}] (B-5);
}

Raw:

Define inverting tools.
\makeatletter{
\def\orangeshark{}
\def\pgfsyssoftpath@linetotoken#1#2{
\xdef\orangeshark{
\orangeshark
}
}
\let\pgfsyssoftpath@movetotoken=\pgfsyssoftpath@linetotoken
Invert now!
\bluewhale
Result:
}

Forth step:
Combine red and blue paths, and we are done.

\vskip6em
\tikz{
\def\zoo{\redpanda\orangeshark}
\fill[yellow, use path=\zoo];
\draw[red, use path=\redpanda];
\draw[blue, use path=\bluewhale]
}

\end{document}

# The surface tension

The trick here is to remember extra points, and then later construct a bezier curve using those.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{hobby,decorations}

\begin{document}

\makeatletter

\tikz{
\coordinate (A-1) at (0, 0); \coordinate (B-1) at (0.5, 0);
\coordinate (A-2) at (1, 1); \coordinate (B-2) at (1, 0.5);
\coordinate (A-3) at (3, 1); \coordinate (B-3) at (3, 0.5);
\coordinate (A-4) at (4, 3); \coordinate (B-4) at (4, 2.5);
\coordinate (A-5) at (7, 3); \coordinate (B-5) at (A-5);

\coordinate (C-1) at (0.25, 0);
\coordinate (C-2) at (1, 0.75);
\coordinate (C-3) at (3, 0.75);
\coordinate (C-4) at (4, 2.75);
\coordinate (C-5) at (A-5);

\path (0,0) (4,3);
\draw [red] (A-1) to [curve through={(A-2) (A-3) (A-4)}] (A-5);
\draw [blue] (B-1) to [curve through={(B-2) (B-3) (B-4)}] (B-5);
}

Drawing with surface tension

\pgfdeclaredecoration{curveto ratio}{initial}{%
\state{initial}[
width=0pt, next state=draw
]{}%
\state{draw}[
width=0pt, next state=check
]{
\pgfpathlineto{\pgfpointorigin}
}%
\state{check}[
width=\pgfdecoratedinputsegmentlength/10, next state=draw,
switch if less than=.384*\pgfdecoratedpathlength to final
]{}%
\state{final}{
% this is new; we want to remember points
% remember the origin as the end point
\pgfpointtransformed{\pgfpointorigin}
\xdef\remember@endpoint@x{\the\pgf@x}
\xdef\remember@endpoint@y{\the\pgf@y}
% remember a far away point as the control point
\pgfpointtransformed{\pgfqpoint{5pt}{0pt}}
\xdef\remember@control@x{\the\pgf@x}
\xdef\remember@control@y{\the\pgf@y}
}%
}%
\tikz{
\path (0,0) (4,3);
\tikzset{decoration={curveto ratio}}
% process red curve
\draw [red, decorate, save path=\redpanda]
(A-1) to [curve through={(A-2) (A-3) (A-4)}] (A-5);
% rename the remembered points
%\let\red@endpoint@x=\remember@endpoint@x % unused
%\let\red@endpoint@y=\remember@endpoint@y % unused
\let\red@control@x=\remember@control@x
\let\red@control@y=\remember@control@y
% process blue curve
\draw [blue, decorate, save path=\bluewhale]
(B-1) to [curve through={(B-2) (B-3) (B-4)}] (B-5);
% rename the remembered points
\let\blue@endpoint@x=\remember@endpoint@x
\let\blue@endpoint@y=\remember@endpoint@y
\let\blue@control@x=\remember@control@x
\let\blue@control@y=\remember@control@y
{% invert the blue whale as before
\def\orangeshark{}
\def\pgfsyssoftpath@linetotoken#1#2{
\xdef\orangeshark{
\orangeshark
}
}
\let\pgfsyssoftpath@movetotoken=\pgfsyssoftpath@linetotoken
\bluewhale
}
% construct a curve (the "surface tension" part) that will connect red and blue.
\def\greensnake{
\pgfsyssoftpath@curvetosupportatoken{\red@control@x}{\red@control@y}%control1
\pgfsyssoftpath@curvetosupportbtoken{\blue@control@x}{\blue@control@y}%contr2
\pgfsyssoftpath@curvetotoken{\blue@endpoint@x}{\blue@endpoint@y} % the target
}
% insert this curve between the two tokens
\def\zoo{\redpanda\greensnake\orangeshark}
% and we are ready to paint
\path(0,0)(4,3);
\fill[yellow, use path=\zoo];
}

\end{document}

# Precision concern

To control the precise stopping point, I need to rewrite the decoration automata.

The basic idea is to keep track of how far away we are from the target. If far, set the step length to the default value. If close enough, set the step length to be the remaining distance.

The current version and handle percentages 1%, 2%, ..., 99% pretty well. 100% is difficult because rounding errors add up at the end of the path. (Perhaps it is easier to just fill the entire area.)

\documentclass[tikz]{standalone}
\usetikzlibrary{hobby}
\usetikzlibrary{decorations}

\begin{document}

\makeatletter

\tikz{
\coordinate (A-1) at (0, 0); \coordinate (B-1) at (0.5, 0);
\coordinate (A-2) at (1, 1); \coordinate (B-2) at (1, 0.5);
\coordinate (A-3) at (3, 1); \coordinate (B-3) at (3, 0.5);
\coordinate (A-4) at (4, 3); \coordinate (B-4) at (4, 2.5);
\coordinate (A-5) at (7, 3); \coordinate (B-5) at (A-5);

\coordinate (C-1) at (0.25, 0);
\coordinate (C-2) at (1, 0.75);
\coordinate (C-3) at (3, 0.75);
\coordinate (C-4) at (4, 2.75);
\coordinate (C-5) at (A-5);

\path (0,0) (4,3);
\draw [red] (A-1) to [curve through={(A-2) (A-3) (A-4)}] (A-5);
\draw [blue] (B-1) to [curve through={(B-2) (B-3) (B-4)}] (B-5);
}

\def\ratio{.382}
\newlength\distancetotarget
\newlength\recommendstep
\newlength\babystep

\pgfdeclaredecoration{curveto ratio}{prepare}{%
\state{prepare}[
persistent precomputation={
\pgfmathsetlength\distancetotarget{\ratio*\pgfdecoratedpathlength}
\pgfmathsetlength\recommendstep{\pgfdecoratedinputsegmentlength/16}
% decrease this length to improve precision
},
width=0pt, next state=travel and draw
]{}%
\state{travel and draw}[
width=\babystep
]{
\ifdim\distancetotarget>\recommendstep% long journey to go
\global\babystep\recommendstep % move by default step length
\xdef\pgf@decorate@next@state{travel and draw}%
\else % close to the targeted point
\global\babystep\distancetotarget% move carefully
\xdef\pgf@decorate@next@state{final}%
\fi
\global\advance\distancetotarget by-\babystep% on step closer to the target
\pgfpathlineto{\pgfpointorigin}% draw
}%
\state{final}{}%
}%
\foreach\index in{0,...,9,51,52,...,59,91,92,...,100}{
\def\ratio{\index/100}
\par\tikz{
\path (0,0) (4,3);
\tikzset{decoration={curveto ratio}}
% process red curve
\draw [red, decorate, save path=\redpanda]
(A-1) to [curve through={(A-2) (A-3) (A-4)}] (A-5);
% rename the remembered points
% process blue curve
\draw [blue, decorate, save path=\bluewhale]
(B-1) to [curve through={(B-2) (B-3) (B-4)}] (B-5);
% rename the remembered points
{% invert the blue whale as before
\def\orangeshark{}
\def\pgfsyssoftpath@linetotoken##1##2{
\xdef\orangeshark{
\orangeshark
}
}
\let\pgfsyssoftpath@movetotoken=\pgfsyssoftpath@linetotoken
\bluewhale
}
\def\zoo{\redpanda\orangeshark}
% and we are ready to paint
\path(0,0)(4,3);
\fill[yellow, use path=\zoo];
}
}

\end{document}

• Looks pretty good. I attempted to packge this up and have added that to the question for now and included some feedback on the results. If you want to copy/adapt that and include it here I can deleted it from the question. – Peter Grill Aug 24 '20 at 6:37
• @PeterGrill The (only) problem is the resolution. The line width=\pgfdecoratedinputsegmentlength/5 controls the "unit length" of the broken lines. By making this smaller, percentages less than 5% or more than 95% will make sense. I'll update an answer later. – Symbol 1 Aug 24 '20 at 16:20

Here's a solution using clipping. We use the markings decoration to find points at the right proportion along each curve, then draw a line through those points and clip to one side of it by drawing a very big rectangle.

It won't work in all situations -- for example, if the curve twists too much -- but is simple enough that when it does work there's little computation to be done.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{hobby,calc}
\usetikzlibrary{decorations.markings}

\tikzset{Label Points on Path/.style n args={3}{
decoration={
markings,
mark=
between positions #1 and #2 step #3
with
{
\draw [fill=blue] (0,0) circle (2pt);
}
},
postaction=decorate,
}}
\tikzset{Fill Points on Path/.style n args={3}{%% <-- Needed for hack solution
decoration={
markings,
mark=
between positions #1 and #2 step #3
with
{
\draw [fill=cyan] (0,0) circle (7pt);% large circle

\draw [fill=cyan] (0,0) circle (7pt);% large circle
}
},
postaction=decorate,
}}

\begin{document}
\begin{tikzpicture}
\coordinate (A-1) at (0, 0); \coordinate (B-1) at (0.5, 0);
\coordinate (A-2) at (1, 1); \coordinate (B-2) at (1, 0.5);
\coordinate (A-3) at (3, 1); \coordinate (B-3) at (3, 0.5);
\coordinate (A-4) at (4, 3); \coordinate (B-4) at (4, 2.5);
\coordinate (A-5) at (7, 3); \coordinate (B-5) at (A-5);

\coordinate (C-1) at (0.25, 0);
\coordinate (C-2) at (1, 0.75);
\coordinate (C-3) at (3, 0.75);
\coordinate (C-4) at (4, 2.75);
\coordinate (C-5) at (A-5);

\draw [black, fill=yellow!15]
(A-5)
to[out=-90, in=0, distance=5.0cm]
(A-1)
to [curve through={(A-2) (A-3) (A-4)}]
(A-5);

%% Hack solution
%\draw [gray, thin, dotted, Fill Points on Path={0.02}{0.2}{0.01}]
%    (C-1)
%    to [curve through={(C-2) (C-3) (C-4)}]
%    (C-5);

\path[use Hobby shortcut,
decoration={
markings,
mark=at position .3 with {\coordinate (A-30);}
},
decorate
] (A-1) .. (A-2) .. (A-3) .. (A-4) .. (A-5);

\path[use Hobby shortcut,
decoration={
markings,
mark=at position .3 with {\coordinate (B-30);}
},
decorate
] (B-1) .. (B-2) .. (B-3) .. (B-4) .. (B-5);

\begin{scope}[overlay]
\coordinate (cl-1) at ($(A-30)!30cm!(B-30)$);
\coordinate (cl-2) at ($(B-30)!30cm!(A-30)$);
\coordinate (cl-3) at ($(cl-1)!30cm!90:(A-30)$);
\coordinate (cl-4) at ($(cl-2)!30cm!-90:(B-30)$);
\clip (cl-1) -- (cl-2) -- (cl-4) -- (cl-3) -- cycle;
\fill[use Hobby shortcut,red!50]  (A-1) .. (A-2) .. (A-3) .. (A-4) .. (A-5) -- (B-5) .. (B-4) .. (B-3) .. (B-2) .. (B-1) -- cycle;
\end{scope}

\draw [ultra thick, red]
(A-1)
to [curve through={(A-2) (A-3) (A-4)}]
(A-5);

\draw [thin, blue] (B-1)
to [curve through={(B-2) (B-3) (B-4)}]
(B-5);

\draw [gray, thick, dotted, Label Points on Path={0.2}{1}{0.4}]
(C-1)
to [curve through={(C-2) (C-3) (C-4)}]
(C-5);

\end{tikzpicture}
\end{document}

Only for fun and for comparison with my Asymptote ability.

Andew Stacey's code

unitsize(1cm);
size(300);
pair A[]={(0,0),(1,1),(3,1),(4,3),(7,3)};
pair B[]={(0.5,0),(1,.5),(3,.5),(4,2.5),(7,3)};
pair C[]={(0.25,0),(1,.75),(3,.75),(4,2.75),(7,3)};

draw(A[4]{dir(-90)}..{dir(180)}A[0]..operator ..(... A),black);
// I don't know the Asymptote equivalent of distance=5.0cm
path pathA=operator ..(... A),
pathB=operator ..(... B),
pathC=operator ..(... C);
draw(pathA,red+1bp);
draw(pathB,blue);
draw(pathC,gray+dotted);

dot(C,blue);

guide percentpath(real n=0.5){
path subpathA=subpath(pathA,reltime(pathA,0),reltime(pathA,n));
path subpathB=subpath(pathB,reltime(pathB,0),reltime(pathB,n));
return subpathA--relpoint(subpathB,1)--reverse(subpathB)--cycle;
}
fill(percentpath(0.3),red+opacity(.5));

This is my try with thinking Hobby curve is a spline interpolation algorithm.

import animate;
usepackage("amsmath");
settings.tex="pdflatex";

animation Ani;
import graph;

unitsize(4cm,1cm);
real f(real x){ return -x^2+4*x+3;}
real g(real x){ return -x^3+7*x^2-10*x+5;}
path F=graph(f,0,3,350),G=graph(g,0,3,350);
pair S[]=intersectionpoints(F,G);
for(int a=0; a<=100;a=a+2)
{
save();
draw(F,blue);
draw(G,red);

draw(Label("$x$",EndPoint),(0,0)--(3.5,0),Arrow);
draw(Label("$y$",EndPoint),(0,0)--(0,10.5),Arrow);
real marginx=0.05, marginy=0.2;

for (real u=0; u <= 10.0; u=u+1){
draw(scale(0.6)*Label("$"+(string) u+"$",Relative(0)),(0,u)--(0,u)+(marginx,0));
}
for (real u=0; u<= 3; u=u+1){
draw(scale(0.6)*Label("$"+(string) u+"$",Relative(0)),(u,0)--(u,0)+(0,marginy));
}
dot(S);

guide percentpath(real percent=0.5, path g, path h){
path subpathg=subpath(g,reltime(g,0),reltime(g,percent));
path subpathh=subpath(h,reltime(h,0),reltime(h,percent));
return subpathg--relpoint(subpathh,1)--reverse(subpathh)--cycle;
}

real sim=simpson(new real(real x){return f(x)-g(x);},S[0].x,S[1].x);
real m=S[1].x-S[0].x;
real simpercent=simpson(new real(real x){return f(x)-g(x);},S[0].x,S[0].x+a/100*m);
fill(percentpath(1,graph(f,S[0].x,S[0].x+a/100*m,350),graph(g,S[0].x,S[0].x+a/100*m,350)),red+opacity(.5));
label("Sim = $"+ (string) sim+" (100 \%)$",(2.5,1));
label("Simpercent = $"+ (string) (simpercent/sim*100) +" \%$",(2.5,2));
restore();
}
erase();
Ani.movie(BBox(2mm,Fill(white)));

Gif with https://ezgif.com/pdf-to-gif

• Thanks for you answer. I haven't used Asymptote yet so can't evaluate this answer at this time. However, I do want to get to Asymtote later, so this is very useful. – Peter Grill Aug 30 '20 at 21:06

## First try

The idea is from this post.

Use record={...} to record path.

Use \pfill[<path options>]{<path 1>}{<path 2>}{<start pos>}{<end pos>} to fill the region.

\documentclass[tikz, border=1cm]{standalone}
\usetikzlibrary{decorations.markings, hobby, backgrounds}

\makeatletter
\tikzset{
record/.style={
/utils/exec=\tikzset{partial fill/.cd, #1},
postaction=decorate, decoration={
markings,
mark=between positions 0 and 0.99 step 0.01 with {
\pgfkeysgetvalue{/pgf/decoration/mark info/sequence number}\coorcnt
\pgfmathtruncatemacro{\coorcnt}{\coorcnt-1}
\path (0, 0) coordinate (\pfill@name-c\coorcnt);
},
mark=at position 0.999999 with {
\path (0, 0) coordinate (\pfill@name-c100);
},
}
},
partial fill/.search also=/tikz,
partial fill/.cd,
name/.store in=\pfill@name,
name=,
}
\newcommand\pfill[5][yellow]{
\scoped[on background layer]
\fill[#1] plot[variable=\t, samples at={#4,...,#5}, hobby] (#2-c\t) --
plot[variable=\t, samples at={#5,...,#4}, hobby] (#3-c\t) -- cycle;
}
\makeatother

\begin{document}
\begin{tikzpicture}
\coordinate (A-1) at (0, 0); \coordinate (B-1) at (0.5, 0);
\coordinate (A-2) at (1, 1); \coordinate (B-2) at (1, 0.5);
\coordinate (A-3) at (3, 1); \coordinate (B-3) at (3, 0.5);
\coordinate (A-4) at (4, 3); \coordinate (B-4) at (4, 2.5);
\coordinate (A-5) at (7, 3); \coordinate (B-5) at (A-5);
\draw [ultra thick, red, record={name=a}]
(A-1)
to [curve through={(A-2) (A-3) (A-4)}]
(A-5);
\draw [thin, blue, record={name=b}]
(B-1)
to [curve through={(B-2) (B-3) (B-4)}]
(B-5);
\pfill{a}{b}{2}{30}
\pfill[teal]{a}{b}{45}{70}
\end{tikzpicture}
\end{document}
• This seems to the easiest to adapt in terms of the interface, but am running into Dimension too large issues. – Peter Grill Aug 26 '20 at 1:17
• As original post mentioned: "This solution makes use of decorations, so for very curvy paths it can lead to dimension too large errors" , but at leat this code does not have this issue, it can run in overleaf. – ZhiyuanLck Aug 26 '20 at 2:16

A second solution which is faster and, in my opinion, more natural

(The code for the above image can be found at the end of this answer. It produces a number of images that are grouped afterward in a 'gif file.)

The idea is almost the same (see the initial solution below), but instead of using a "parametrization" of the B-curve, it uses "parametrizations" of both curves. The filling is given by joining points corresponding to the same value of the parameter (almost).

• The argument \s controls the width (in pt) of the step of the parametrizations. The parametrizations (i.e. the two sets of points along the curves) are introduced, as before, through a decoration. There is the decoration A steps (and B steps) that computes the number of points for the A-curve, and the decoration marked points which constructs the points. The argument of marked points modifies the name of the points.
• The filling is realized by the pic element which takes as arguments the ratio and the number of points for the two parametrizations. (This code is long because the number of points, more often than not, is not the same for the A- and for the B-curve.)

In the test hereafter, I modified the initial points (A-i) such that their x-coordinates are not increasing anymore.

The same filling but with a larger step, \s=7; the segments are easier to see. Note that there are two segments issued from one B-point from time to time. This is the reason for the length of the pic's code.

The code of this new solution is below:

\documentclass[11pt, border=1cm]{standalone}

\usepackage{tikz}
\usetikzlibrary{calc, math, intersections, hobby}
\usetikzlibrary{decorations.markings}

\begin{document}

\tikzset{%
A steps/.style args={of width#1}{%
decorate, decoration={markings,
mark=at position 0 with {%
\tikzmath{%
int \APoints;
real \dl;
\APoints = int(\pgfdecoratedpathlength/#1);
\dl = 1/\APoints;
}
\pgfextra{\xdef\APoints{\APoints}}
\pgfextra{\xdef\dl{\dl}}
}
}
},
B steps/.style args={of width#1}{%
decorate, decoration={markings,
mark=at position 0 with {%
\tikzmath{%
int \BPoints;
real \dl;
\BPoints = int(\pgfdecoratedpathlength/#1);
\dl = 1/\BPoints;
}
\pgfextra{\xdef\BPoints{\BPoints}}
\pgfextra{\xdef\dl{\dl}}
}
}
},
marked points/.style={%
decorate, decoration={markings,
mark=between positions 0 and 1 step \dl with {
\path (0, 0) coordinate[
name=m#1-\pgfkeysvalueof{/pgf/decoration/mark info/sequence number}
];
}
}
},
pics/partial filling/.code args={ratio#1with#2A points and#3B points}{
\tikzmath{
int \N, \i, \j, \k, \d;
if #3<#2 then {
\d = int(#3/(#2-#3));
\N=#1*#3;
for \i in {1,...,\N}{%
\j = \i + int(\i/\d);
{
\draw[green!50!yellow!50, line width=2pt, line cap=round]
(mA-\j) -- ($(mA-\j)!.94!(mB-\i)$);
};
if \i==int(\i/\d)*\d then {
\k = \j-1;
{
\draw[green!50!yellow!50, line width=2pt, line cap=round]
(mA-\k) -- ($(mA-\k)!.94!(mB-\i)$);
};
};
};
} else {
if #3==#2 then {
\N=#1*#3;
for \i in {1,...,\N}{%
{
\draw[green!50!yellow!50, line width=2pt, line cap=round]
(mA-\i) -- ($(mA-\i)!.94!(mB-\i)$);
};
};
} else {
\d = int(#2/(#3-#2));
\N=#1*#2;
for \i in {1,...,\N}{%
\j = \i + int(\i/\d);
{
\draw[green!50!yellow!50, line width=2pt, line cap=round]
(mA-\i) -- ($(mA-\i)!.94!(mB-\j)$);
};
if \i==int(\i/\d)*\d then {
\k = \j-1;
{
\draw[green!50!yellow!50, line width=2pt, line cap=round]
(mA-i) -- ($(mA-i)!.94!(mB-\k)$);
};
};
};
};
};
}
}
}
\tikzmath{ real \s; \s=1.7; }
\begin{tikzpicture}
\coordinate (A-1) at (0, 0);
\coordinate (A-2) at (1, 1);
\coordinate (A-3) at (3, 1);
\coordinate (A-4) at (3, 3);
\coordinate (A-5) at (7, 3);
\coordinate (B-1) at (0.5, 0);
\coordinate (B-2) at (1, 0.5);
\coordinate (B-3) at (3.4, 0.7);
\coordinate (B-4) at (3.5, 2.9);
\coordinate (B-5) at (A-5);

%% the setup
\draw[black, fill=yellow!15] (A-5)
to[out=-90, in=0, distance=5.0cm] (A-1)
to[curve through={(A-2) (A-3) (A-4)}] (A-5);

%% partial filling
\path[preaction={A steps={of width \s}}, postaction={marked points=A}]
(A-1) to [curve through={(A-2) (A-3) (A-4)}] (A-5);
\path[preaction={B steps={of width \s}},  postaction={marked points=B}]
($(A-1)!.2!(B-1)$) -- (B-1)
to [curve through={(B-2) (B-3) (B-4)}] (B-5);
\draw pic {
partial filling={ratio .7 with \APoints A points and \BPoints B points}
};

%% the two curves
\draw[ultra thick, red] (A-1)
to [curve through={(A-2) (A-3) (A-4)}] (A-5);

\draw[thin, blue] (B-1)
to [curve through={(B-2) (B-3) (B-4)}] (B-5);
\draw (A-1) -- (B-1);
\end{tikzpicture}

\end{document}

First solution

The solution is inspired by the computation (in calculus) of the area enclosed in-between two curves; the idea is to move along the lower curve and, at each point'', to construct the segment joining it with the corresponding point (for a fixed direction) on the upper curve. Imagine this segment as a vertical bar.

1. Using a decoration, we obtain sufficiently many points on the lower curve. I decided to use a 1.7pt step along the curve.

2. The vertical bars (I'm working with the vertical direction) are constructed through a pics ... code key depending on an argument that defines the ratio of the filling.

3. For the solution to work with a beautifully smooth result, the upper and lower curves must be introduced as paths for the filling. Then, they are drawn over the filling.

\documentclass[11pt, border=1cm]{standalone}

\usepackage{tikz}
\usetikzlibrary{calc, math, intersections, hobby}
\usetikzlibrary{decorations.markings}

\begin{document}

\tikzset{%
marked points/.style={%
decorate, decoration={markings,
mark=at position 0 with {%
\tikzmath{%
real \tmp, \dl;
\tmp=\pgfdecoratedpathlength;
\nPoints = int(\tmp/1.7);
\dl = 1/\nPoints;
}
\pgfextra{\xdef\nPoints{\nPoints}}  % passed to the next mark
\pgfextra{\xdef\dl{\dl}}
\path (0, 0) coordinate[name=marked-1];
},
mark=between positions 0 and .98 step \dl with {
\path (0, 0) coordinate[
name=marked-\pgfkeysvalueof{/pgf/decoration/mark info/sequence number}
];
}
}
},
pics/partial filling/.code args={ratio#1with#2}{
\tikzmath{int \N; \N=#1*#2;}
\foreach \i in {1, ..., \N}{%
\path (marked-\i);
\pgfgetlastxy{\ax}{\ay}
\path[name path=vertical] (marked-\i) -- ++(0, 2);
\path[name intersections={of=vertical and upper, by={P}}];
\draw[green!50!yellow!50, line width=2pt, line cap=round]
($(marked-\i)+(0, 1pt)$) -- ($(P)-(0, 1pt)$);
}
}
}
\begin{tikzpicture}
\coordinate (A-1) at (0, 0);
\coordinate (A-2) at (1, 1);
\coordinate (A-3) at (3, 1);
\coordinate (A-4) at (4, 3);
\coordinate (A-5) at (7, 3);
\coordinate (B-1) at (0.5, 0);
\coordinate (B-2) at (1, 0.5);
\coordinate (B-3) at (3, 0.5);
\coordinate (B-4) at (4.2, 2.5); % (4, 2.5);
\coordinate (B-5) at (A-5);

%% the setup
\draw[black, fill=yellow!15] (A-5) to[out=-90, in=0, distance=5.0cm] (A-1)
to[curve through={(A-2) (A-3) (A-4)}] (A-5);

%% partial filling
\path[name path=upper] (A-1) to [curve through={(A-2) (A-3) (A-4)}] (A-5);
\path[postaction={marked points}]
($(A-1)+(2pt, 0)$) -- (B-1) to [curve through={(B-2) (B-3) (B-4)}] (B-5);
\draw pic {partial filling={ratio .7 with \nPoints}};

%% the two curves
\draw[ultra thick, red] (A-1) to [curve through={(A-2) (A-3) (A-4)}] (A-5);
\draw[thin, blue] (B-1) to [curve through={(B-2) (B-3) (B-4)}] (B-5);
\end{tikzpicture}

\end{document}

The code at the origin of the animation at the beginning.

\documentclass[11pt, border=1cm, multi=my, crop]{standalone}

\usepackage{tikz}
\usetikzlibrary{calc, math, intersections, hobby}
\usetikzlibrary{decorations.markings}

\colorlet{tmprgb}{blue!50!red!20}

\tikzset{%
A steps/.style args={of width#1}{%
decorate, decoration={markings,
mark=at position 0 with {%
\tikzmath{%
int \APoints;
real \dl;
\APoints = int(\pgfdecoratedpathlength/#1);
\dl = 1/\APoints;
}
\pgfextra{\xdef\APoints{\APoints}}
\pgfextra{\xdef\dl{\dl}}
}
}
},
B steps/.style args={of width#1}{%
decorate, decoration={markings,
mark=at position 0 with {%
\tikzmath{%
int \BPoints;
real \dl;
\BPoints = int(\pgfdecoratedpathlength/#1);
\dl = 1/\BPoints;
}
\pgfextra{\xdef\BPoints{\BPoints}}
\pgfextra{\xdef\dl{\dl}}
}
}
},
marked points/.style={%
decorate, decoration={markings,
mark=between positions 0 and 1 step \dl with {
\path (0, 0) coordinate[
name=m#1-\pgfkeysvalueof{/pgf/decoration/mark info/sequence number}
];
}
}
},
pics/partial filling/.code args={ratio#1with#2A points and#3B points}{
\tikzmath{
int \N, \i, \j, \k, \d;
if #3<#2 then {
\d = int(#3/(#2-#3));
\N=#1*#3;
for \i in {1,...,\N}{%
\j = \i + int(\i/\d);
{
\draw[tmprgb, line width=2pt, line cap=round]
(mA-\j) -- ($(mA-\j)!.94!(mB-\i)$);
};
if \i==int(\i/\d)*\d then {
\k = \j-1;
{
\draw[tmprgb, line width=2pt, line cap=round]
(mA-\k) -- ($(mA-\k)!.94!(mB-\i)$);
};
};
};
} else {
if #3==#2 then {
\N=#1*#3;
for \i in {1,...,\N}{%
{
\draw[tmprgb, line width=2pt, line cap=round]
(mA-\i) -- ($(mA-\i)!.94!(mB-\i)$);
};
};
} else {
\d = int(#2/(#3-#2));
\N=#1*#2;
for \i in {1,...,\N}{%
\j = \i + int(\i/\d);
{
\draw[tmprgb, line width=2pt, line cap=round]
(mA-\i) -- ($(mA-\i)!.94!(mB-\j)$);
};
if \i==int(\i/\d)*\d then {
\k = \j-1;
{
\draw[tmprgb, line width=2pt, line cap=round]
(mA-i) -- ($(mA-i)!.94!(mB-\k)$);
};
};
};
};
};
}
}
}

\begin{document}
%\foreach \iterator in {.1,.2,.3,.4,.5,.6,.7,.8,.9,.98}{%
\foreach \iterator in {0,.033,.066,...,.98}{%
\begin{my}
\begin{tikzpicture}
\tikzmath{ real \s; \s=1.7; }

%\draw[help lines] (0, 0) grid (9, 7);
\path
(0, .5) coordinate (A-1)
++(5, -.5) coordinate (A-2)
++(3, 3) coordinate (A-3)
++ (-2, 3) coordinate (A-4)
++ (-4, 0) coordinate (A-5)
++ (-1, -3) coordinate (A-6)
++ (4, -1) coordinate (A-7);
\path
(.8, 1.25) coordinate (B-1)
++(4, -.75) coordinate (B-2)
++(2.25, 3) coordinate (B-3)
++ (-1.5, 1.5) coordinate (B-4)
++ (-3, 0) coordinate (B-5)
++ (-.7, -2) coordinate (B-6)
(A-7) coordinate (B-7);

\path[preaction={A steps={of width \s}}, postaction={marked points=A}]
(A-1) to[curve through={(A-2) (A-3) (A-4) (A-5) (A-6)}] (A-7);
\path[preaction={B steps={of width \s}},  postaction={marked points=B}]
(B-1) to[curve through={(B-2) (B-3) (B-4) (B-5) (B-6)}] (B-7);
\draw pic {
partial filling={ratio \iterator with \APoints A points and \BPoints B points}
};

\draw[red, line width=1.5pt]
(A-1) to[curve through={(A-2) (A-3) (A-4) (A-5) (A-6)}] (A-7);
\draw[blue, line width=1.3pt] (A-1)
-- (B-1) to[curve through={(B-2) (B-3) (B-4) (B-5) (B-6)}] (B-7);
\end{tikzpicture}
\end{my}
}

\end{document}
• Where is the code for the image shown at the top of this answer? Also, bear with me as I distribute the bounites as promised within the software constaints imposed by StackExchange -- which means that I need to do them in a particular order and timeframes. – Peter Grill Aug 30 '20 at 21:04
• The code is a variation of the second solution. I'll add it to my answer at the end. It brings nothing to the comprehension of the second solution. – Daniel N Aug 31 '20 at 5:16
• Don't worry about the bounties. It is not they which triggered my interest; it is the question. I'm curious if the solution works in your context. Now, looking back, I think that the solution can be simplified. In fact, it must turn around the number of points, and only one single number of points. The idea is the following: if the B-curve is a small deformation of the A-curve, then a parametrization of A deforms into a parametrization of B. Of course, it all depends on which curve is longer. But we, the creators of the curves, we must know this a priori. – Daniel N Aug 31 '20 at 6:21
• What is the my environment? – Peter Grill Sep 6 '20 at 19:22
• multi=my yields an environment ("my" environment) that splits the standalone into pages with one tikzpicture for each page. I needed this output such that, afterward, to be able to split the pages into independent pdf files. With these files (transformed into pngs) I created the gif animation. Without using "my", the output is a row of tikz pictures on one page. – Daniel N Sep 7 '20 at 4:49

Even though this was a tikz question, I hope there is room for a MetaPost answer. The following code generates a 101 pages pdf file (running context on the file), which animated looks like below. The (parts of the) two paths are joined via a bezier curve with "correct"(?) directions at the endpoints, creating the "circular-ish bulge" you asked for.

\define[1]\hobbyfill{%
\startMPpage
u:=2cm;

path hobby[];

hobby0 = ((0,0)..(1,1)..(3,1)..(4,3)..(7,3)) scaled u;
hobby1 = ((0.5,0)..(1,0.5)..(3,0.5)..(4,2.5)..(7,3)) scaled u;

fill (hobby0 cutafter point #1/100 along hobby 0)
.. (reverse (hobby1 cutafter point #1/100 along hobby 1))
-- cycle
withcolor darkyellow;

draw hobby0 withcolor darkred;
draw hobby1 withcolor darkblue;
\stopMPpage
}

\starttext
\hobbyfill{0}
\dorecurse{100}{\hobbyfill{\recurselevel}}
\stoptext

• I do really like the circular-ish bulge in this animation. – Peter Grill Aug 31 '20 at 8:23

This is a second answer using a different method. In this version, once we have found the points at which we want to truncate the curves then we exploit a feature of Hobby's algorithm to regenerate the curves to those points. That feature is that adding a point that is already on the curve doesn't change the result of the algorithm. So once we have the stopping points, re-running Hobby's algorithm with those points added in gives a new set of beziers that run exactly along the original set with the added advantage that the point we wish to stop at is an end point of one of the beziers. Throwing away the rest of the path then yields the truncated path.

The rest is then manipulating the two part-paths into a region that can be filled. I brought in some heavy machinery here in the guise of my spath3 library to reverse one of the segments.

The part that is not automatic is figuring out where the new point should be added to the curve since that will change as it passes the existing points.

(Incidentally, while poking around in my code for this, I came across How to split a (Hobby) path in two about splitting curves which might make this a bit easier, but that was from a few years back so I don't remember all that it does.)

\documentclass{article}
%\url{https://tex.stackexchange.com/q/559582/86}
\usepackage{tikz}
\usetikzlibrary{hobby}
\usetikzlibrary{decorations.markings}

\usepackage{spath3}

\tikzset{Label Points on Path/.style n args={3}{
decoration={
markings,
mark=
between positions #1 and #2 step #3
with
{
\draw [fill=blue] (0,0) circle (2pt);
}
},
postaction=decorate,
}}
\tikzset{Fill Points on Path/.style n args={3}{%% <-- Needed for hack solution
decoration={
markings,
mark=
between positions #1 and #2 step #3
with
{
\draw [fill=cyan] (0,0) circle (7pt);% large circle
}
},
postaction=decorate,
}}

\ExplSyntaxOn

% small hack to fix a bug
\cs_set_eq:NN \prop_gpop:Nn \prop_gremove:Nn

% Code to shorten a hobby-defined path by removing segments
\cs_new_nopar:Npn \hobby_gpop:
{
\int_decr:N \g__hobby_npoints_int
\array_gpop:NN \g__hobby_controla_array \l_tmpa_tl
\array_gpop:NN \g__hobby_controlb_array \l_tmpa_tl
\array_gpop:NN \g__hobby_points_array \l_tmpa_tl
\array_gpop:NN \g__hobby_actions_array \l_tmpa_tl
}

% Wrapper for the above in a tikzset
\tikzset{
pop~ Hobby~ path/.code~ 2~ args={
\pgfextra{
\hobbyrestorepath{#1}
\prg_replicate:nn {#2}
{
\hobby_gpop:
}
\hobbysavepath{#1}
}
},
% Bug in the spath3 code
insert~ spath/.code={
\spath_get_current_path:n {current path}
\spath_weld:nn { current path } { #1 }
\spath_set_current_path:n { current path }
},
}

\ExplSyntaxOff

\def\pathpos{.3}

\begin{document}
\begin{tikzpicture}
\coordinate (A-1) at (0, 0); \coordinate (B-1) at (0.5, 0);
\coordinate (A-2) at (1, 1); \coordinate (B-2) at (1, 0.5);
\coordinate (A-3) at (3, 1); \coordinate (B-3) at (3, 0.5);
\coordinate (A-4) at (4, 3); \coordinate (B-4) at (4, 2.5);
\coordinate (A-5) at (7, 3); \coordinate (B-5) at (A-5);

\coordinate (C-1) at (0.25, 0);
\coordinate (C-2) at (1, 0.75);
\coordinate (C-3) at (3, 0.75);
\coordinate (C-4) at (4, 2.75);
\coordinate (C-5) at (A-5);

% Find the points that lie at the given proportion along each curve.
\path[
use Hobby shortcut,
decoration={
markings,
mark=at position \pathpos with {
\coordinate (A-stop);
}
},
decorate
] (A-1) .. (A-2) .. (A-3) .. (A-4) .. (A-5);

\path[
use Hobby shortcut,
decoration={
markings,
mark=at position \pathpos with {
\coordinate (B-stop);
}
},
decorate
] (B-1) .. (B-2) .. (B-3) .. (B-4) .. (B-5);

% Useful to figure out which specified points our stopping point lies between
%\foreach \k in {1,...,5} \fill (A-\k) circle[radius=2mm];

% Generate the paths with the new point included
% This is the bit that might be tricky to automate
\path[
use Hobby shortcut,
save Hobby path=A,
] (A-1) .. (A-2) .. (A-stop) .. (A-3) .. (A-4) .. (A-5);

\path[
use Hobby shortcut,
save Hobby path=B
] (B-1) .. (B-2) .. (B-stop) .. (B-3) .. (B-4) .. (B-5);

% Shorten the paths by removing the last three segments
\tikzset{pop Hobby path={A}{3}}
\tikzset{pop Hobby path={B}{3}}

% Convert the shortened paths to spath3 objects
\path[
restore and use Hobby path=A{disjoint},
save spath=A
];
\path[
restore and use Hobby path=B{disjoint},
save spath=B,
];

% Reverse the segment of the B-path
\tikzset{reverse spath=B}

\draw [black, fill=yellow!15]
(A-5)
to[out=-90, in=0, distance=5.0cm]
(A-1)
to [curve through={(A-2) (A-3) (A-4)}]
(A-5);

% Fill the region between the two paths
\fill[
red!50,
restore spath=A,
] -- (B-stop) [insert spath=B];

\draw [ultra thick, red]
(A-1)
to [curve through={(A-2) (A-3) (A-4)}]
(A-5);

\draw [thin, blue] (B-1)
to [curve through={(B-2) (B-3) (B-4)}]
(B-5);

\draw [gray, thick, dotted, Label Points on Path={0.2}{1}{0.4}]
(C-1)
to [curve through={(C-2) (C-3) (C-4)}]
(C-5);

%% Hack solution
%\draw [gray, thin, dotted, Fill Points on Path={0.02}{0.2}{0.01}]
%    (C-1)
%    to [curve through={(C-2) (C-3) (C-4)}]
%    (C-5);

\end{tikzpicture}
\end{document}
• I missed the bit about wanting the connection to be curved. That wouldn't actually be difficult since we have a bezier ending/starting at the given points so we have the tangent directions. It's just a matter of extracting that information and putting it in to the joining part of the path. – Andrew Stacey Aug 26 '20 at 19:07
• I get Undefined control sequence. <argument> \g__prg_map_int with TeXLive2020 updated as of today. – Peter Grill Aug 27 '20 at 1:50
• Hmm, thought I'd fixed all of those. Clearly one didn't get to ctan. It is presumably in spath3. In the meantime, you can get it from github, github.com/loopspace/spath3 – Andrew Stacey Aug 27 '20 at 7:19