问题
I am producing the figure below using the following gnuplot code. I want to draw a bended arrow from the point labeled l=0 to l=1 with head.
Code
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
How do I do that?
回答1:
I'm not aware that gnuplot offers a feature for directly drawing a bent arrow.
Edit: (I removed my initial approach since it has no advantage over using Cubic Bézier. And added some more flexibility to the second approach.)
I completely agree with @GRSousaJr that Cubic Bézier curves give much more flexibility in drawing bent arrows. At the same time you can also draw straight arrows.
Based on @GRSousaJr's approach, my suggestions would be the following:
instead of entering absolute values for the control points, I would prefer relative or absolute angles and relative distances. This has the advantage that you don't have to care about absolute numbers, especially when two arrows should have the same proportions but have different absolute start/endpoints.
All parameters for the arrows are in the datablock $myArrows
.
Some explanations:
for the Cubic Bézier curves 4 points are used:
p0,p1,p2,p3
, wherep0
andp3
are the start and end points, respectively.p1
andp2
are points which control the curvature.p0x, p0y, ... p3x, p3y
are thex
andy
components, respectively.in contrast to @GRSousaJr's solution the control points
p1
andp2
are not given in absolute values but calculated fromp0
andp3
and the anglesa0
anda3
and the radiir0
andr3
.the angles of the arrow at the points
p0
andp3
can be given absolute or relative to the direction ofp0
top3
. The parametere
tells which end has relative angle and which end absolute angle. 0=angles at both ends relative, 1=start angle relative, end angle absolute, 2=start angle absolute, end angle relative, 3=angles at both ends absolute. For the relative angle you first need to calculate the angle betweenp0
andp3
(functionAngleP0P3()
)the distance of the control points
p1
andp2
from the pointsp0
andp3
are given in relative valuesr0
andr3
with respect to the distance betweenp0
andp3
. That's why there is the functionLength()
.0.5
is a good value to start with.note that the functions
AngleP0P3(n)
andLength(n)
actually do not depend onn
. That is just to shorten the code. These functions use the parametersp1x, ..., p3y
, and when callingAngleP0P3(0)
the function will take the current values ofp1x, ..., p3y
. This is shorter than e.g.Angle(p0x,p0y,p3x,p3y)
.the function
ArrowInit(i)
is to collect or initialize the values forp1x, ..., p3y
from thei
th row of datablock$myArrows
.the line of the arrows are simply plotted in a for loop as parametric function in
t
with the ranget[0:1]
. For everyi
in the plot commandArrowInit(i)
is called to get the corresponding parameters from the datablock$myArrows
.The angle of the arrow in point
p3
is in the direction fromp2
top3
, i.e. the tangent of the Bézier curve in pointp3
. However you don't want the line, but just the arrow. So far, I don't have a better approach than plotting a short vector from 99% of the arrow path to 100% of the arrow path.
Some comments on usage:
in order to "see" the correct angles you specify in
$myArrows
, your plot has to have the same aspect ratio as your x and y ranges. In the below examples it isx[0:20]
andy[0:10]
, hence, set the aspect ratio of the graph to0.5
, i.e. at the beginningset size 0.5
.the direction of the arrow head is the tangent in point
p3
. If you have a strong curvature atp3
, the arrow head might look "bad", although the arrow head is in the correct angle. In such cases, increase the lengthr3
a little.You can also draw straight arrows, see Arrow1. Just set
a0=0,a3=0
ande=0
.
Tested with gnuplot 5.2.8
Code:
### 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
Result:
回答2:
I created (at least in my mind) an "enhanced" version of @theozh's answer, which allows somewhat control over arrow form.
The idea is to use a Bézier curve to draw the bent arrow. The head
is drawn as on @theozh's answer, i.e., using vectors
. The initial (xi,yi
) and final points (xy,yf
), as well the control points (xc1,yc1
and xc2,yc2
), are passed to a function using call
command. The function creates a datafile using theid
, like a tag
on standard arrow
, defined by user, and associates a variavel (e.g. BentArrow_id
) name to such datafile. Each created datafile contain:
- the control points
- the datapoints to create the
arrow
, and - the datapoints do create
head
as three indexable datablocks (0
, 1
and 2
, respectively), like this:
# 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
To draw the bent arrow, the plot
command must be composed by three parts:
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 ...,\
...
Each part corresponds to a piece of arrow (the control points, the arrow itself, and the head, respectively).
To better show the script (called BentArrow.fct
) working, consider the example.
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
The results
Applying the script to your example, the result look like this
Of course the control points are useful just to defining each arrow.
The variable showCtrlPoints = "False" (default "True")
is defined to allows hide the control points on final plot.
The script to last example is:
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
The BentArrow.fct
file contain:
# 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
Improvements will be well received!
来源:https://stackoverflow.com/questions/60380161/draw-a-bended-arrow-between-two-points-in-gnuplot