Нарисуйте изогнутую стрелку между двумя точками в gnuplot - PullRequest
2 голосов
/ 24 февраля 2020

Я создаю рисунок ниже, используя следующий код gnuplot. Я хочу нарисовать изогнутую стрелку от точки, обозначенной l = 0 до l = 1 с головкой.

Код

reset session
# Ranges
set xrange [-1:6]
set yrange [-2:1]
# Term options
set terminal postscript eps 
set termoption font "Times, 30"
# set termoption
set style line 1 lc rgb 'black' lw 3 lt 1 pt 7 ps 2
# Data points
$DATA<<EOD
0   0 
1   0
2   0
3   0
4   0
5   0
6   0
EOD 
set output "Anderson_lattice.eps"
# set arrow
set arrow 1 from -0.5, -1.5 to 5.5, -1.5 lc rgb 'black' lw 5 
set arrow 2 from -0.5, -1.5 to -0.5, -0.5 lc rgb 'black' lw 5
set label 1 "{/Times-Italic=30 {/Symbol e}_{l}}" at -0.75, -0.3 tc rgb "black"
set arrow 3 from -0.25, -1.0 to 0.25, -1.0 ls 1 nohead
set arrow 5 from 1 - 0.25, -0.75 to 1 + 0.25, -0.75 ls 1 nohead
set arrow 6 from 2 - 0.25, -0.5 to 2 + 0.25, -0.5 ls 1 nohead
set arrow 7 from 3 - 0.25, -1.35 to 3 + 0.25, -1.35 ls 1 nohead
set arrow 8 from 4 - 0.25, -1.0 to 4 + 0.25, -1 ls 1 nohead
set arrow 9 from 5 - 0.25, -0.85 to 4 + 0.25, -0.85 ls 1 nohead
set arrow 10 from 6 - 0.25, -1.25 to 6 + 0.25, -1.25 ls 1 nohead
set label 2 "{/Times-Italic=30 sites}" at 5.5, -1.65 tc 'black'
set label 3 "{/Times-Italic=30 l=0}" at 2.7, -0.25 tc 'black'
set label 4 "{/Times-Italic=30 l=1}" at 1 + 2.7, -0.25 tc 'black'
unset xtics; unset ytics; unset border
plot $DATA using 1:2 with p ls 1 notitle
unset output

Результат Result of the code above

Как мне это сделать?

Ответы [ 2 ]

4 голосов
/ 25 февраля 2020

Я не знаю, что gnuplot предлагает функцию для непосредственного рисования изогнутой стрелки.

Редактировать: (Я удалил свой первоначальный подход, так как он не имеет преимуществ перед использованием Cubi c Bézier. И добавил немного большей гибкости ко второму подходу.)

Я полностью согласен с @GRSousaJr, что кривые Куби c Безье дают гораздо большую гибкость при рисовании изогнутых стрелок. В то же время вы также можете рисовать прямые стрелки.

Исходя из подхода @ GRSousaJr, мои предложения будут следующими: вместо ввода абсолютных значений для контрольных точек я бы предпочел относительные или абсолютные углы и относительные расстояния , Это имеет то преимущество, что вам не нужно заботиться об абсолютных числах, особенно когда две стрелки должны иметь одинаковые пропорции, но иметь разные абсолютные начальные / конечные точки. Все параметры для стрелок находятся в блоке данных $myArrows.

Некоторые пояснения:

  1. для кубических кривых Безье 1151 * 4 балла используются: p0,p1,p2,p3, где p0 и p3 - начальная и конечная точки соответственно. p1 и p2 - это точки, которые контролируют кривизну. p0x, p0y, ... p3x, p3y - компоненты x и y соответственно.

  2. В отличие от решения @ GRSousaJr, контрольные точки p1 и p2 не приведены в абсолютных значениях но вычисляется из p0 и p3 и углов a0 и a3 и радиусов r0 и r3.

  3. углов стрелы на Точки p0 и p3 могут быть заданы абсолютными или относительно направления от p0 до p3. Параметр e указывает, какой конец имеет относительный угол, а какой - абсолютный угол. 0 = углы на обоих концах относительно, 1 = начальный угол относительно, конечный угол абсолютный, 2 = начальный угол абсолютный, конечный угол относительный, 3 = углы на обоих концах абсолютные. Для относительного угла сначала необходимо вычислить угол между p0 и p3 (функция AngleP0P3())

  4. расстояние от контрольных точек p1 и p2 из точек p0 и p3 даны в относительных значениях r0 и r3 относительно расстояния между p0 и p3. Вот почему есть функция Length(). 0.5 - хорошее значение для начала.

  5. обратите внимание, что функции AngleP0P3(n) и Length(n) на самом деле не зависят от n. Это просто для сокращения кода. Эти функции используют параметры p1x, ..., p3y, и при вызове AngleP0P3(0) функция принимает текущие значения p1x, ..., p3y. Это короче, чем, например, Angle(p0x,p0y,p3x,p3y).

  6. . Функция ArrowInit(i) предназначена для сбора или инициализации значений для p1x, ..., p3y из i-го ряда блока данных $myArrows.

  7. Линия стрелок просто построена в для l oop в качестве параметра c в t с диапазоном t[0:1]. Для каждого i в сюжете вызывается команда ArrowInit(i) для получения соответствующих параметров из блока данных $myArrows.

  8. Угол наклона стрелки в точке p3 находится в направление от p2 до p3, т. е. касательная кривой Безье в точке p3. Однако вам не нужна линия, а только стрелка. Пока что у меня нет лучшего подхода, чем построение короткого вектора от 99% пути стрелки до 100% пути стрелки.

Некоторые комментарии по использованию :

  1. Чтобы "увидеть" правильные углы, которые вы указываете в $myArrows, ваш график должен иметь такое же соотношение сторон, что и диапазоны x и y. В приведенных ниже примерах это x[0:20] и y[0:10], следовательно, установите соотношение сторон графика на 0.5, то есть в начале set size 0.5.

  2. направление стрелка является касательной в точке p3. Если у вас сильная кривизна на p3, головка стрелки может выглядеть «плохо», хотя головка стрелки находится под правильным углом. В таких случаях немного увеличьте длину r3.

  3. Вы также можете нарисовать прямые стрелки, см. Стрелка1. Просто установите a0=0,a3=0 и e=0.

Протестировано с gnuplot 5.2.8

Код:

### workaround for bent arrows
reset session
set size ratio 0.5

#    p0x   p0y    a0   r0   p3x   p3y   a3   r3  e     color
$myArrows <<EOD                    
 1  1.00  1.00     0  0.5  3.00  3.00    0  0.5  0  0xff0000
 2  3.00  1.00     0  0.5  5.00  3.00    0  0.5  1  0x00c000
 3  5.00  1.00     0  0.5  7.00  3.00    0  0.5  2  0x0000ff
 4  7.00  1.00     0  0.5  9.00  3.00    0  0.5  3  0xff00ff
 5  1.00  4.00     0  0.5  3.00  6.00   90  0.5  0  0xff0000
 6  3.00  4.00     0  0.5  5.00  6.00   90  0.5  1  0x00c000
 7  5.00  4.00     0  0.5  7.00  6.00   90  0.5  2  0x0000ff
 8  7.00  4.00     0  0.5  9.00  6.00   90  0.5  3  0xff00ff
 9  1.00  7.00    90  0.5  3.00  9.00    0  0.5  0  0xff0000
10  3.00  7.00    90  0.5  5.00  9.00    0  0.5  1  0x00c000
11  5.00  7.00    90  0.5  7.00  9.00    0  0.5  2  0x0000ff
12  7.00  7.00    90  0.5  9.00  9.00    0  0.5  3  0xff00ff
13 11.00  1.00    45  0.5 13.00  3.00  -45  0.5  0  0xff0000
14 13.00  1.00    45  0.5 15.00  3.00  -45  0.5  1  0x00c000
15 15.00  1.00    45  0.5 17.00  3.00  -45  0.5  2  0x0000ff
16 17.00  1.00    45  0.5 19.00  3.00  -45  0.5  3  0xff00ff
17 11.00  4.00   -45  0.5 13.00  6.00  -45  0.5  0  0xff0000
18 13.00  4.00   -45  0.5 15.00  6.00  -45  0.5  1  0x00c000
19 15.00  4.00   -45  0.5 17.00  6.00  -45  0.5  2  0x0000ff
20 17.00  4.00   -45  0.5 19.00  6.00  -45  0.5  3  0xff00ff
21 11.00  7.00     0  0.5 15.00  9.00   90  0.5  1  0x00c000
22 15.00  7.00     0  0.5 19.00  9.00    0  0.5  1  0x00c000
EOD

set angle degrees
# Angle between p0 and p3 (range: -90° <= angle < 270°), NaN if dx=dy=0
AngleP0P3(n)  = (dy=p3y-p0y,dx=p3x-p0x)==0 ? (dy==0 ? NaN : sgn(dy)*90) : \
                (dx<0 ? 180 : 0) + atan(dy/dx)

# Parameter e: determines which ends have relative or absolute angles
# 0: both ends relative
# 1: start relative, end absolute,
# 2: start absolute, end relative
# 3: both ends absolute
AngleAbs(i) = int(word($myArrows[i],10))   # to set all arrows equal, use: AngleAbs(i) = 0,1,2, or 3
Angle(i,p) = word($myArrows[i],p) + \
             ((p==4 && AngleAbs(i)&2) || (p==8 && AngleAbs(i)&1) ? 0 : AngleP0P3(0))
Length(n) = sqrt((p3x-p0x)**2 + (p3y-p0y)**2)
Color(i)  = word($myArrows[i],11)

ArrowInit(i) = (p0x=word($myArrows[i],2),p0y=word($myArrows[i],3), \
           p3x=word($myArrows[i],6),p3y=word($myArrows[i],7), \
           p1x=p0x+Length(0)*word($myArrows[i],5)*cos(Angle(i,4)), \
           p1y=p0y+Length(0)*word($myArrows[i],5)*sin(Angle(i,4)), \
           p2x=p3x-Length(0)*word($myArrows[i],9)*cos(Angle(i,8)), \
           p2y=p3y-Length(0)*word($myArrows[i],9)*sin(Angle(i,8)))

# Cubic Bézier curves function with t[0:1] as parameter
# p0: start point, p1: 1st control point, p2: 2nd control point, p3: endpoint
px(t) = (-p0x + 3*p1x - 3*p2x + p3x)*t**3 + (3*p0x - 6*p1x + 3*p2x)*t**2 + (-3*p0x + 3*p1x)*t + p0x
py(t) = (-p0y + 3*p1y - 3*p2y + p3y)*t**3 + (3*p0y - 6*p1y + 3*p2y)*t**2 + (-3*p0y + 3*p1y)*t + p0y

# set linestyles and arrowstyles
do for [i=1:|$myArrows|] {
    set style line i lw 2 lc rgb Color(i)
    set style arrow i head size 0.20,15,45 fixed filled ls i
}

set key out noautotitle below
set xrange [0:20]
set xtics 1
set format x ""
set grid xtics ls -1 lc rgb "gray"
set yrange [0:10]
set ytics 1
set format y ""
set grid ytics ls -1 lc rgb "gray"

plot for [i=1:|$myArrows|] [0:1] '+' u (ArrowInit(i),px($1)):(py($1)) w l ls i, \
     for [i=1:|$myArrows|] [0:1] '+' u (ArrowInit(i),px(0.99)):(py(0.99)): \
     (px(1)-px(0.99)):(py(1)-py(0.99)) every ::0::0 w vec as i, \
     $myArrows u 2:3:1 w labels offset 0,-0.7, \
     keyentry w l ls 1 ti "both ends relative angles", \
     keyentry w l ls 2 ti "start relative, end absolute angle", \
     keyentry w l ls 3 ti "start absolute, end relative angle", \
     keyentry w l ls 4 ti "both ends absolute angles"
### end of code

exit

Результат:

enter image description here

3 голосов
/ 26 февраля 2020

Я создал (по крайней мере, на мой взгляд) «улучшенную» версию ответа @ theozh , которая позволяет в некоторой степени контролировать форму стрелки.

Идея состоит в том, чтобы использовать кривую Безье , чтобы нарисовать изогнутую стрелку. head рисуется как в ответе @ theozh, т. Е. С использованием vectors. Начальная (xi,yi) и конечная точки (xy,yf), а также контрольные точки (xc1,yc1 и xc2,yc2) передаются в функцию с помощью команды call. Функция создает файл данных, используя id, как tag в стандартном arrow, определенном пользователем, и связывает имя переменной (например, BentArrow_id) с таким файлом данных. Каждый созданный файл данных содержит:

  1. контрольные точки
  2. точки данных для создания arrow и
  3. точки данных создают head

как три индексируемых блока данных (0, 1 и 2 соответственно), например:

# Block index 0 (control points)
 1.000000e+00   -1.250000e+00
 1.250000e+00    0.000000e+00

 2.800000e+00   -5.000000e-01
 3.000000e+00   -7.500000e-01


# Block index 1 (arrow)
 1.000000e+00   -1.250000e+00
 1.016539e+00   -1.177084e+00
 1.036070e+00   -1.108272e+00
 1.058468e+00   -1.043468e+00
 ...             ...
 2.927437e+00   -6.862240e-01
 2.949992e+00   -7.027320e-01
 2.969690e+00   -7.189280e-01
 2.986401e+00   -7.347160e-01
 3.000000e+00   -7.500000e-01


# Block index 2 (head)
 2.986401e+00   -7.347160e-01    1.359880e-02   -1.528400e-02
 3.000000e+00   -7.500000e-01    0.000000e+00    0.000000e+00

Чтобы нарисовать изогнутую стрелку, команда plot должна быть состоит из трех частей:

plot \
    ...
    BentArrow_id i 0 u 1:2 w lp ...,\
    BentArrow_id i 1 u 1:2 w lines ...,\
    BentArrow_id i 2 u 1:2:3:4 w vectors ...,\
   ...

Каждая часть соответствует куску стрелки (контрольные точки, сама стрелка и головка соответственно).

Чтобы лучше показать сценарий ( называется BentArrow.fct) работает, рассмотрим пример.

reset
set terminal wxt size 500,500
set size ratio -1
set grid ls -1 lc "gray"
unset key
set tics out nomirror
set xrange [-0.25:9.25]
set yrange [-0.25:9.25]

set style arrow 1 head size 0.25,15,45 fixed filled lc "red"

BentArrow(id,xi,yi,x1,y1,x2,y2,xf,yf) = \
    sprintf("call 'BentArrow.fct' '%g' '%f' '%f' '%f' '%f' '%f' '%f' '%f' '%f'", \
        id, xi,yi, x1,y1, x2,y2, xf,yf )

#             id,  xi,yi , xc1,yc1, xc2,yc2,  xf,yf
eval BentArrow(1, 1.0,1.0, 2.0,2.0, 3.0,0.0, 4.0,1.0)
eval BentArrow(2, 5.0,1.0, 6.0,0.0, 7.0,2.0, 8.0,1.0)
eval BentArrow(3, 1.0,4.0, 2.0,3.0, 3.0,3.0, 4.0,4.0)
eval BentArrow(4, 5.0,4.0, 6.0,5.0, 7.0,5.0, 8.0,4.0)
eval BentArrow(5, 1.0,7.0, 5.0,5.0, 0.0,5.0, 4.0,7.0)
eval BentArrow(6, 5.0,7.0, 5.0,9.0, 6.0,7.0, 8.0,7.0)

CtrlPoints = "w lp ls -1 pt 6 ps 1 pi -1"
StyleArrow = "w lines lc 'red' lw 2"
StyleHead  = "w vec as 1"

plot \
    BentArrow_1 i 0 u 1:2     @CtrlPoints ,\
    BentArrow_1 i 1 u 1:2     @StyleArrow ,\
    BentArrow_1 i 2 u 1:2:3:4 @StyleHead  ,\
    BentArrow_2 i 0 u 1:2     @CtrlPoints ,\
    BentArrow_2 i 1 u 1:2     @StyleArrow ,\
    BentArrow_2 i 2 u 1:2:3:4 @StyleHead  ,\
    BentArrow_3 i 0 u 1:2     @CtrlPoints ,\
    BentArrow_3 i 1 u 1:2     @StyleArrow ,\
    BentArrow_3 i 2 u 1:2:3:4 @StyleHead  ,\
    BentArrow_4 i 0 u 1:2     @CtrlPoints ,\
    BentArrow_4 i 1 u 1:2     @StyleArrow ,\
    BentArrow_4 i 2 u 1:2:3:4 @StyleHead  ,\
    BentArrow_5 i 0 u 1:2     @CtrlPoints ,\
    BentArrow_5 i 1 u 1:2     @StyleArrow ,\
    BentArrow_5 i 2 u 1:2:3:4 @StyleHead  ,\
    BentArrow_6 i 0 u 1:2     @CtrlPoints ,\
    BentArrow_6 i 1 u 1:2     @StyleArrow ,\
    BentArrow_6 i 2 u 1:2:3:4 @StyleHead 

Результаты

bent arrow example

Применение сценария к вашему примеру, результат выглядит следующим образом

result with control points

Конечно, контрольные точки полезны только для определения каждой стрелки.
Переменная showCtrlPoints = "False" (default "True") определена для позволяет скрыть контрольные точки на конечном графике.

result without control points

Сценарий до последнего примера:

reset

# The data
$levels<<EOD
 1   0.5   -1.25            
 2   0.5   -1.00
 3   0.5   -0.75
 4   0.5   -0.50
 5   0.5   -0.25
 6   0.5   -1.25
 7   0.5   -1.00
 8   0.5   -0.75
 9   0.5   -0.50
10   0.5   -0.25
EOD

# Cubic Bézier function
BentArrow(id,xi,yi,x1,y1,x2,y2,xf,yf) = \
    sprintf("call 'BentArrow.fct' '%g' '%f' '%f' '%f' '%f' '%f' '%f' '%f' '%f'", \
        id, xi,yi, x1,y1, x2,y2, xf,yf )

# Arrow styles
set style arrow 1 head size 0.2,15,45 fixed filled lc "red"
set style arrow 2 head size 0.2,15,45 fixed filled lc "web-green"
set style arrow 3 head size 0.2,15,45 fixed filled lc "blue"

# To levels
set errorbars small
unset key

# Options to drawing the bent arrows
showCtrlPoints = "False"
ArrowPoints = 50

# Calling the function
eval BentArrow(1,  1.00,-1.25,  1.25, 0.00,  2.80,-0.50,  3.00,-0.75)
eval BentArrow(2,  8.00, 0.50,  8.00, 0.00,  5.00, 0.25,  5.00,-0.25)
eval BentArrow(3,  1.00, 0.50,  2.00,-0.25,  9.00, 0.50,  10.0,-0.25)

# Macros
Points = "w p ls -1 pt 7 ps 2"
Levels = "w xerrorbars ls -1 lw 2"
CtrlPoints = "w lp ls -1 pt 6 ps 1 pi -1"
StyleArrow = "w lines lw 2"
StyleHead  = "w vectors"

# Allow to toggle between show/hide the control points
CP(n) = showCtrlPoints eq "True" ? n : NaN

plot \
    $levels u 1:2                @Points                    ,\
    "" u 1:3:(0.35)              @Levels                    ,\
    BentArrow_1 i 0 u 1:(CP($2)) @CtrlPoints                ,\
    BentArrow_1 i 1 u 1:2        @StyleArrow lc "red"       ,\
    BentArrow_1 i 2 u 1:2:3:4    @StyleHead as 1            ,\
    BentArrow_2 i 0 u 1:(CP($2)) @CtrlPoints                ,\
    BentArrow_2 i 1 u 1:2        @StyleArrow lc "web-green" ,\
    BentArrow_2 i 2 u 1:2:3:4    @StyleHead as 2            ,\
    BentArrow_3 i 0 u 1:(CP($2)) @CtrlPoints                ,\
    BentArrow_3 i 1 u 1:2        @StyleArrow lc "blue"      ,\
    BentArrow_3 i 2 u 1:2:3:4    @StyleHead as 3 


Файл BentArrow.fct содержит:

# Implements a bent arrow using Cubic Bézier curves (https://en.wikipedia.org/wiki/Bézier_curve)
#
# Usage: call 'BentArrow.fct' tag xi yi yc1 yc1 xc2 yc2 xf yf
# where
#       xi,yi   = start point
#       xc1,yc1 = control point #1
#       xc2,yc2 = control point #2
#       xf,yf   = final point
#
# The algorithm creates 
#     1) a variable named BentArrow_id with 'id' as a integer number,
#        defined by user like a standart arrow, and 
#     2) a datafile (e.g BentArrow_id.bentarrow) containing 
#          i) the control points, 
#         ii) the datapoints to create the curve, and
#        iii) the datapoints do ceate head
# as indexable datablocks (0, 1 and 2, respectively).
# The number of datapoint (samples) used on bent arrow construction
# are defined by 'ArrowPoints' (default 50)

# Receiving the arguments from 'call' command
tag = int(ARG1)
x_i = real(ARG2)
y_i = real(ARG3)
x_1 = real(ARG4)
y_1 = real(ARG5)
x_2 = real(ARG6)
y_2 = real(ARG7)
x_f = real(ARG8)
y_f = real(ARG9)

# Defining the variable to filename, based on 'tag', and creating the datafile
eval sprintf( "%s_%g = %s", 'BentArrow', tag, sprintf("'BentArrow_%g.bentarrow'", tag) )

# Checking if 'ArrowPoints' is defined
if ( !exists("ArrowPoints") ) {
    ArrowPoints = 50
}

# Quadratic Bézier function
DrawArrow(t,p0,p1,p2,p3) = (1-t)**3*p0 + 3*(1-t)**2*t*p1 + 3*(1-t)*t**2*p2 + t**3*p3 # 0 <= t <= 1

# Creating the datafile containing the datapoints to bent arrow
set print sprintf('BentArrow_%g.bentarrow', tag)
    # ----- ControlPoints -----------------------
    print "# Block index 0 (control points)"
    print sprintf("% e\t% e", x_i, y_i)
    print sprintf("% e\t% e", x_1, y_1)
    print ""
    print sprintf("% e\t% e", x_2, y_2)
    print sprintf("% e\t% e", x_f, y_f)
    print ""
    print ""
    # ----- ArrowData -----------------------
    print "# Block index 1 (arrow)"
    do for [i=0:int(ArrowPoints):1] {
        t = i/real(ArrowPoints)
        print sprintf("% e\t% e", DrawArrow(t,x_i,x_1,x_2,x_f), DrawArrow(t,y_i,y_1,y_2,y_f))
    }
    print ""
    print ""
    # ----- ArrowHead -----------------------
    print "# Block index 2 (head)"
    do for [i=int(ArrowPoints)-1:int(ArrowPoints):1] {
        t = i/real(ArrowPoints)
        x_head = x_f - DrawArrow(t,x_i,x_1,x_2,x_f)
        y_head = y_f - DrawArrow(t,y_i,y_1,y_2,y_f)
        print sprintf("% e\t% e\t% e\t% e", DrawArrow(t,x_i,x_1,x_2,x_f), DrawArrow(t,y_i,y_1,y_2,y_f), x_head, y_head)
    }
unset print 

Улучшения будут хорошо приняты!

...