Let's assume a sequence of points. If I would like to plot a smooth curve through these points,
I thought the plot option smooth
would do the job.
From help smooth
:
Syntax:
smooth {unique | frequency | fnormal | cumulative | cnormal | bins | kdensity {bandwidth} | csplines | acsplines | mcsplines | bezier | sbezier | unwrap}
So, Bézier curves will not go through the points, but some of the splines
should.
However, in gnuplot splines
require monotonic x-values. If they are not monotonic, gnuplot will make them monotonic,
with (in this case) undesired results.
How can I draw a smooth curve through the points?
Example:
### smooth curve through points?
reset session
set size ratio -1
$Data <<EOD
0 0
2 3
4 2
9 3
5 7
3 6
4 5
5 5
4 4
1 6
1 4
3 10
EOD
set key out
set ytics 1
plot $Data u 1:2 w lp pt 7 lc "red" dt 3 ti "data", \
'' u 1:2 w l smooth bezier lc "green" ti "bézier", \
'' u 1:2 w l smooth csplines lc "orange" ti "csplines", \
'' u 1:2 w l smooth mcsplines lc "magenta" ti "mcsplines", \
'' u 1:2 w l smooth acsplines lc "yellow" ti "acsplines"
### end of code
Result: (none of the smooth
options will give the desired result)
Edit: Here is a completely revised and shortened version:
r
to tune the shape of the curve.To my opinion, the "nicest" Bézier curve through the given points is plotted with the parameter r=0.333
.
As mentioned by @binzo, since gnuplot5.5, you have the option smooth path
which is drawn for comparison.
Script: (requires gnuplot>=5.2.0 because of arrays, and gnuplot>=5.5 because of smooth path
)
(skip the second plot line if you have gnuplot<5.5)
### plot cubix Bézier curve through given points
reset session
$Data <<EOD
0 0
2 3
4 2
9 3
5 7
3 6
4 5
5 5
4 4
1 6
1 4
3 10
EOD
set size ratio -1
set angle degrees
set key noautotitles reverse Left
set samples 200
colX = 1
colY = 2
j = {0,1} # imaginary unit
a(dx,dy) = dx==0 && dy==0 ? NaN : atan2(dy,dx) # angle of segment between two points
L(dx,dy) = sqrt(dx**2 + dy**2) # length of segment
r = 0.333 # relative distance of ctrl points
stats $Data u 0 nooutput # get number of points+1
N = STATS_records+1
array P0[N]
array PA[N]
array PB[N]
array P1[N]
x1=x2=y1=y2=ap1=NaN
stats $Data u (x0=x1, x1=x2, x2=column(colX), i=int($0)+1, \
y0=y1, y1=y2, y2=column(colY), P0[i]=x0+j*y0, \
dx1=x1-x0, dy1=y1-y0, d1=L(dx1,dy1), dx1n=dx1/d1, dy1n=dy1/d1, \
dx2=x2-x1, dy2=y2-y1, d2=L(dx2,dy2), dx2n=dx2/d2, dy2n=dy2/d2, \
a1=a(dx1,dy1), a2=a(dx2,dy2), a1=a1!=a1?a2:a1, \
ap0=ap1, ap1=a(cos(a1)+cos(a2),sin(a1)+sin(a2)), \
PA[i]=x0+d1*r*cos(ap0) + j*(y0+d1*r*sin(ap0)), \
PB[i]=x1-d1*r*cos(ap1) + j*(y1-d1*r*sin(ap1)), P1[i]=x1+j*y1, 0) nooutput
# add last segment
P0[i+1] = x1+j*y1
PA[i+1] = x1+d1*r*cos(ap1)+j*(y1+d1*r*sin(ap1))
PB[i+1] = x2-d2*r*cos(a2) +j*(y2-d2*r*sin(a2))
P1[i+1] = x2+j*y2
# Cubic Bézier function with t[0:1] as parameter between two points
# p0: start point, pa: 1st ctrl point, pb: 2nd ctrl point, p1: endpoint
p(i,t) = t**3 * ( -P0[i] + 3*PA[i] - 3*PB[i] + P1[i]) + \
t**2 * ( 3*P0[i] - 6*PA[i] + 3*PB[i] ) + \
t * (-3*P0[i] + 3*PA[i] ) + P0[i]
plot $Data u 1:2 w lp pt 7 lc "red" dt 3 ti "data", \
'' u 1:2 smooth path w l lc "black" ti "smooth path", \
for [i=2:|P0|] [0:1] '+' u (real(p(i,$1))):(imag(p(i,$1))) w l lc "blue" \
ti i==2?("\nCubic Bézier\nthrough points"):''
### end of script
Result:
And for fun, an animation, varying the parameter r
: