Search code examples
gnuplotcurvesmoothing

How to plot a smooth line through a sequence of points with gnuplot?


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


Solution

  • 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