如何使用 gnuplot 通过一系列点绘制平滑线?

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

让我们假设一系列点。如果我想通过这些点绘制一条平滑的曲线, 我认为绘图选项 smooth 可以完成这项工作。

来自help smooth

Syntax:

  smooth {unique | frequency | fnormal | cumulative | cnormal | bins
                 | kdensity {bandwidth}
                 | csplines | acsplines | mcsplines | bezier | sbezier
                 | unwrap}

因此,贝塞尔曲线不会 通过 点,但某些 splines 应该。 但是,在 gnuplot splines 中需要单调的 x 值。如果它们不是单调的,gnuplot 将使它们成为单调的, 有(在这种情况下)不希望的结果。

如何通过 个点绘制平滑曲线

示例:

### 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

结果:smooth 选项中的 none 将给出所需的结果)

这是一个有点冗长的解决方案,它至少给出了一些 acceptable 结果。 它基于 中的代码。 该代码将创建一个 table,其中包含三次贝塞尔曲线的参数。

如果有更简单的解决方案,请告诉我。

代码:

### plot smoothed curve through given 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 angle degrees
Angle(dx,dy) = (_l=sqrt(dx**2 + dy**2), _l==0 ? NaN : dy/_l >= 0 ? acos(dx/_l) : -acos(dx/_l) )

# get points and angles of segments
set table $PointsAndAngles
    array Dummy[1]
    plot x1=x2=y1=y2=NaN $Data u (x0=x1,x1=x2):(y0=y1,y1=y2):(x2=):(y2=): \
         (dx1=x1-x0, dy1=y1-y0, dx2=x2-x1, dy2=y2-y1, \
         dx2==dx2 && dy2==dy2 && dx1==dx1 && dy1==dy1 ? \
         (d1=sqrt(dx1**2+dy1**2), d2=sqrt(dx2**2+dy2**2), \
         a2=Angle(dx2,dy2), a3=Angle(dx1/d1+dx2/d2,dy1/d1+dy2/d2)) : \
         (d2=sqrt(dx2**2+dy2**2), a2=Angle(dx2,dy2))) : (d2) w table
    plot Dummy u (x2):(y2):(NaN):(NaN):(a2):(NaN) w table
unset table

# create table with smooth parameters
# Cubic Bézier curves function with t[0:1] as parameter
# p0: start point, p1: 1st ctrl point, p2: 2nd ctrl point, p3: endpoint
# a0, a3: angles
# r0, r3: radii
#n   p0x   p0y    a0   r0   p3x   p3y   a3   r3     color
set print $SmoothLines
    do for [i=1:|$PointsAndAngles|-1] {
        p0x = word($PointsAndAngles[i],1)
        p0y = word($PointsAndAngles[i],2)
        a0  = word($PointsAndAngles[i],5)
        r0  = 0.3
        p3x = word($PointsAndAngles[i],3)
        p3y = word($PointsAndAngles[i],4)
        a3  = word($PointsAndAngles[i+1],5)
        r3  = 0.3
        color = 0x0000ff
        print sprintf("%d   %s %s %s %g   %s %s %s %g   %d %d", \
                       i, p0x, p0y, a0, r0, p3x, p3y, a3, r3, color)
    }
set print

p0v(n,v) = word($SmoothLines[n],2+v)       # v=0 --> x, v=1 --> y
a0(n)    = word($SmoothLines[n],4)
r0(n)    = word($SmoothLines[n],5)
p3v(n,v) = word($SmoothLines[n],6+v)       # v=0 --> x, v=1 --> y
a3(n)    = word($SmoothLines[n],8)
r3(n)    = word($SmoothLines[n],9)
color(n) = int(word($SmoothLines[n],10))

Length(x0,y0,x1,y1) = sqrt((x1-x0)**2 + (y1-y0)**2)
d03(n)   = Length(p0v(n,0),p0v(n,1),p3v(n,0),p3v(n,1))
p1v(n,v) = p0v(n,v) + (v==0 ? r0(n)*d03(n)*cos(a0(n)) : r0(n)*d03(n)*sin(a0(n)) )
p2v(n,v) = p3v(n,v) - (v==0 ? r3(n)*d03(n)*cos(a3(n)) : r3(n)*d03(n)*sin(a3(n)) )

# parametric cubic Bézier:
pv(n,v,t) = t**3 * (  -p0v(n,v) + 3*p1v(n,v) - 3*p2v(n,v) + p3v(n,v)) + \
            t**2 * ( 3*p0v(n,v) - 6*p1v(n,v) + 3*p2v(n,v)           ) + \
            t    * (-3*p0v(n,v) + 3*p1v(n,v)                        ) + p0v(n,v)

set key noautotitles
set ytics 1

plot $Data u 1:2 w lp pt 7 lc "red" dt 3 ti "data", \
     for [i=2:|$SmoothLines|] [0:1] '+' u (pv(i,0,)):(pv(i,1,)) w l lc rgb color(i), \
     keyentry w l lc "blue" ti "Cubic Bézier through points"
### end of code

结果:

smooth path,正如@binzo 在评论中所写的,可能会给你想要的结果。 我想分享一种我为个人需要开发的替代方法,与您自己的答案类似,它手动定义平滑函数并迭代所有点。

这里我选择了连接两个连续点的样条,水平起止(即它在(x1,y1)和(x2,y2)处的导数为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

将在每对连续的点之间迭代绘制样条曲线。为了这样做,将数据块存储为数组以方便以后索引:

$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]=,yvals[i]=) noout
}

在迭代图中,每个样条曲线仅在其各自的 [x1:x2] 范围内绘制。为了比较,smooth path也包括在内。

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"

另一种方法是绕过数组的转换,直接访问数据块的元素。例如。 $Data[2]将第二行作为字符串给出,可以用word()分割。为了最终得到正确的浮点运算(而不是整数),数字必须用 real() 包裹起来,这使得绘图命令有点冗长:

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"

这两个平滑选项中哪个更好取决于您最终想要实现的目标。显然,此样条尝试失败,因为两个连续点具有相同的 x 值。