Search code examples
gnuplot

Draw a bended arrow between two points in gnuplot


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 Result of the code above

How do I do that?


Solution

  • 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:

    1. for the Cubic Bézier curves 4 points are used: p0,p1,p2,p3, where p0 and p3 are the start and end points, respectively. p1 and p2 are points which control the curvature. p0x, p0y, ... p3x, p3y are the x and y components, respectively.

    2. in contrast to @GRSousaJr's solution the control points p1 and p2 are not given in absolute values but calculated from p0 and p3 and the angles a0 and a3 and the radii r0 and r3.

    3. the angles of the arrow at the points p0 and p3 can be given absolute or relative to the direction of p0 to p3. The parameter e 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 between p0 and p3 (function AngleP0P3())

    4. the distance of the control points p1 and p2 from the points p0 and p3 are given in relative values r0 and r3 with respect to the distance between p0 and p3. That's why there is the function Length(). 0.5 is a good value to start with.

    5. note that the functions AngleP0P3(n) and Length(n) actually do not depend on n. That is just to shorten the code. These functions use the parameters p1x, ..., p3y, and when calling AngleP0P3(0) the function will take the current values of p1x, ..., p3y. This is shorter than e.g. Angle(p0x,p0y,p3x,p3y).

    6. the function ArrowInit(i) is to collect or initialize the values for p1x, ..., p3y from the ith row of datablock $myArrows.

    7. the line of the arrows are simply plotted in a for loop as parametric function in t with the range t[0:1]. For every i in the plot command ArrowInit(i) is called to get the corresponding parameters from the datablock $myArrows.

    8. The angle of the arrow in point p3 is in the direction from p2 to p3, i.e. the tangent of the Bézier curve in point p3. 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:

    1. 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 is x[0:20] and y[0:10], hence, set the aspect ratio of the graph to 0.5, i.e. at the beginning set size 0.5.

    2. the direction of the arrow head is the tangent in point p3. If you have a strong curvature at p3, the arrow head might look "bad", although the arrow head is in the correct angle. In such cases, increase the length r3 a little.

    3. You can also draw straight arrows, see Arrow1. Just set a0=0,a3=0 and e=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:

    enter image description here