# Canonical way to plot a function along a 2D path defined by a list of points

I have some function f[x_, y_] = Sin[x] Cos[x],say. I want to plot this function in 2d along a path defined by a list of points in the plane, say (0,0), (0,1), (2,2). I want to define a helper function lerp that takes in one input and gets to the corresponding point on the path, such that Plot[f@lerp[t],{t,0,1+Sqrt[5]}] has the desired behaviour.

I'm not sure what the idiomatic Mathematica way is of getting such a function that visits each point at unit velocity along straight lines.

Example of how lerp should behave:

lerp[0] -> {0,0}
lerp[1/2] -> {0,1/2}
lerp[1] -> {0,1}
lerp[2] -> {1/Sqrt[5],1 + 1/(2 Sqrt[5])}
lerp[1+Sqrt[5]] -> {2,2}


I have some Python code that does exactly this, but I'm not sure if there's a more elegant alternative to porting it wholesale.

import numpy as np
from numpy.linalg import norm

# example path:
path = {'O': [0,0], 'X': [1,0], 'Y': [0,1]}

def parameterised_path(path, basis=np.eye(2)):
coords = np.matmul(np.array(list(path.values())), basis)

x0y0 = coords[0]

# stores the times at which special points appear
t_list = [0]
uvec_list = []

for i in range(1,len(path)):
xy = coords[i]
arclen = norm(xy-x0y0)
t_list.append(t_list[i-1]+arclen)
uvec_list.append((xy-x0y0)/arclen)
x0y0 = xy

t_list = np.array(t_list)
uvec_list = np.array(uvec_list)

def p(t):
retval = []
for tau in t:
idx = t_list.searchsorted(tau)
if idx==0:
retval.append(coords[0])
elif idx < len(path):
retval.append(coords[idx-1] + (tau-t_list[idx-1])*uvec_list[idx-1])
return np.array(retval)

return p, t_list

$$$$


If I understand your description correctly, you want to plot a function f against the path length. Toward this aim you search a function that maps the path length onto 2D positions. Between the points the path is a straight line. This can be done by (note that the definition of your example path has an error in the last line):

Take the norm of the differences between 2D points. Accumulation of these differences gives the path length to the points. Finally we need to interpolate linearly between these points. Here is the code with an example:

lerp[path_] := Module[{dis},
dis = Differences[path];
dis = Norm /@ dis;
dis = Accumulate[dis];
dis = Prepend[dis, 0];
Interpolation[Transpose[{dis, path}], InterpolationOrder -> 1]
]

f[x_, y_] = Sin[x] Cos[x];
path = {{0, 0}, {0, 1/2}, {1, 1}, {2, 1}, {2, 2}};
p = lerp[path]
Plot[f @@ p[s], {s, 0, 5/2 + Sqrt[5]/2}]


• came here to post almost exactly this as a less-readable one-liner: given path, lerp = Interpolation[Transpose[{Accumulate[Prepend[Norm /@ Differences[#], 0]], #}] &@path, InterpolationOrder -> 1] (this lerp is slightly different syntactically and conforms more strictly to the expectation that e.g. lerp[0] == First@path.) I guess this suggests that this might indeed be the canonical way to do it! :) Jun 3, 2021 at 9:03
• This is true in this case. However, had the path been specified analytically, then integration of the line element would be the way to go. Jun 3, 2021 at 9:17
pts = {{0, 0}, {0, 1}, {2, 2}};
n = Length[pts];
p[i_] := pts[[i]];
length[i_, j_] := Sum[Norm[p[k + 1] - p[k]], {k, i, j - 1}];
lerp[t_] :=
Piecewise[
Join[Table[{p[i] + (t - length[1, i])*Normalize[p[i + 1] - p[i]],
length[1, i] <= t < length[1, i + 1]}, {i, 1,
n - 2}], {{p[n - 1] + (t - length[1, n - 1])*
Normalize[p[n] - p[n - 1]], length[1, n - 1] <= t}}]];
lerp /@ {0, 1/2, 1, 2, 1 + Sqrt[5]}
ParametricPlot[lerp[t], {t, 0, 10},
Epilog -> {PointSize[Large], Red,
Point[lerp /@ {0, 1/2, 1, 2, 1 + Sqrt[5]}]}]


{{0, 0}, {0, 1/2}, {0, 1}, {2/Sqrt[5], 1 + 1/Sqrt[5]}, {2, 2}}

f[x_, y_] = Sin[x] Cos[x];
Plot[f @@ lerp[t], {t, 0, 10}]
`