1

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)

enter image description here

2
  • 1
    FYI, “smooth path” has been implimented in the development version for such use. Commented Sep 3, 2021 at 12:33
  • @binzo thank you for this info. Good to know. Haven't checked yet the development version. Commented Sep 3, 2021 at 13:23

2 Answers 2

3

Edit: Here is a completely revised and shortened version:

  • it uses arrays instead of datablocks (like in @Eldrad's solution)
  • x,y coordinates are stored in real and imaginary part of complex variables, respectively. This makes the parametric calculation shorter compared to two separate x,y variables.
  • use parameter 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:

enter image description here

And for fun, an animation, varying the parameter r:

enter image description here

Sign up to request clarification or add additional context in comments.

Comments

2

smooth path, as already written by @binzo in the comments, might give you the desired result. I'd like to share an alternative approach that I had developed for personal needs and which, similar to your own answer, defines the smoothing function manually and iterates over all points.

Here I chose a spline that connects two consecutive points and starts and ends horizontally (i.e. its derivative at (x1,y1) and (x2,y2) is 0)

spline(x,x1,y1,x2,y2) = y1+x1**2*(y1-y2)*(3*x2-x1)/(x1-x2)**3 + 6*x1*x2*(y2-y1)/(x1-x2)**3*abs(x) + 3*(y1-y2)*(x1+x2)/(x1-x2)**3*abs(x)**2 + 2*(y2-y1)/(x1-x2)**3*abs(x)**3

The spline is going to be plotted iteratively between each consecutive pair of points. In order to do so it is convenient to store the data block as arrays for later indexing:

$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

stats $Data noout
array xvals[STATS_records]
array yvals[STATS_records]
do for [i=1:|xvals|] {
  stats $Data every ::i-1::i-1 u (xvals[i]=$1,yvals[i]=$2) noout
}

In the iterative plot each single spline is plotted only within its respective [x1:x2] range. For comparison, smooth path is also included.

plot $Data w lp pt 7 lc "red" dt 3,\
 for [i=1:|xvals|-1] [xvals[i]:xvals[i+1]] spline(x,xvals[i],yvals[i],xvals[i+1],yvals[i+1]) lc "blue" not,\
 keyentry w l lc "blue" t "user-defined splines",\
 $Data smooth path lc "black" t "smooth path"

enter image description here

Alternatively, one can circumvent the transformation into arrays and directly access the elements of the data block. E.g. $Data[2] gives the second line as a string, which can be split by word(). In order to get proper floating point operations (instead of integer) in the end, the numbers have to be wrapped by real(), which makes the plotting command a bit more voluminous:

plot $Data w lp pt 7 lc "red" dt 3,\
 for [i=1:|$Data|-1] [word($Data[i],1):word($Data[i+1],1)] spline(x, real(word($Data[i],1)), real(word($Data[i],2)), real(word($Data[i+1],1)), real(word($Data[i+1],2)) ) w l lc "blue" not,\
 keyentry w l lc "blue" t "user-defined splines",\
 $Data smooth path lc "black" t "smooth path"

Which of the two smoothing options is better depends on what you want to achieve in the end. Obviously, this spline attempt fails where two consecutive points have the same x value.

8 Comments

Thank you for your suggestion. I haven't yet looked into splines. In my case I was looking for a smooth curve which is not "changing direction" at certain points, i.e. continuous first derivative. I'm pretty sure somehow you can make your approach also fulfill this requirement and also work for two consecutive points with the same x-value.
@theozh Strictly speaking, the first derivative is already continuous in my answer ;-) However, before I put too much effort in it, could you please specify which type of smoothing/pathing you want, or how the analytical expression of the final curve should be defined? Or maybe smooth path is already fulfilling your needs?
you're right! Sorry, I meant without changing the path direction in a sharp corner. How would you describe this mathematically? smooth path in gnuplot 5.5 will certainly be the easiest way and sufficient, although, I like the shape of the Bézier better than the smooth path and you have some parameters to shape the curve a little. Maybe you also have some additional parameters with smooth path... I haven't installed gnuplot 5.5 yet.
@theozh I don't know how to express the "direction" mathematically, because the final path is not a function (one x has several y values). My solution provides piecewise functions, where the second derivative at the corners is not continuous. For path the concept of derivatives is not applicable. Anyway, I'd still like to clarify: For each two consecutive points, you want to draw a Bézier (because Béziers only go through the first and last point), right? What is your concept for defining the intermediate control points? What are the constraints?
yes, "2nd derivative continuous", that's what I was looking for. For a smooth path the requirement would be that at every point you can create a tangent (which you cannot at a sharp corner). In my Bézier approach, 1. the angles at a path point are the average of the angles of the 2 adjacent segments. 2. the control points are calculated relative to two consecutive points in the path and the average angle from 1. Some more detailed explanations are here: stackoverflow.com/a/60389081 . There are no special constraints for the control points. r=0.3 gives reasonably looking smooth curves.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.