magfan
magfan

Reputation: 373

How to create hypertext arrows in gnuplot?

How can I create hypertext-like vectors or arrows that only appear when the user places the mouse on or near the start/end points? For points it looks like this:

indices = "first second"

plot for [id in indices] "a_test.dat" index id u 1:2

replot for [i=1:words(indices)] "a_test.dat" index word( indices, i ) u 1:2:(sprintf("point = (%d, %d)", column(1), column(2))) w labels hypertext point pt 7 ps 2 lc rgb '#ff000000' notitle

File "a_test.dat":

# first
x1  y1
5   1
6   2
9   8

# second
x2  y2
4   5
8   2
2   7

Upvotes: 1

Views: 195

Answers (3)

theozh
theozh

Reputation: 25684

I guess you can mimic a "hypertext arrow" with a while loop and pause mouse.

For vectors it is better to have start/endpoints in one line, but that is a different topic. I will think about an answer about your other question. Here, for simplicity, it is assumed that you have the start/endpoints already in one line.

  • the data is plotted once and then you enter a while loop (check help while) which you can stop by pressing ESC.

  • when button 1 of the mouse is pressed, the data is plotted again, but additionally, the dataline with the minimal distance to the mouse position (i.e. variables MOUSE_X and MOUSE_Y) is plotted with vectors. However, it seems you can't use MOUSE_X and MOUSE_Y in a function. So, well, then your have to assign it first to variables x0 and y0.

  • you could probably tune the behaviour of the appearance of the arrow, i.e. whether it disappears after some time or if you move on, etc...)

Script: (tested with gnuplot 5.4.1 and wxt and qt terminals)

### show arrow on mouse button press (mimic hypertext arrow)
reset session

$Data <<EOD
# first   # second
x1  y1    x2  y2
5   1     4   5
6   2     8   2
9   8     2   7
EOD

set offsets 1,1,1,1
set key out noautotitle

plot $Data u 1:2 w p pt 7 ps 2 lc "blue" ti "start point", \
        '' u 3:4 w p pt 7 ps 2 lc "red"  ti "end point"

dist(colX,colY) = sqrt((column(colX)-x0)**2 + (column(colY)-y0)**2)

continue = 1
while (continue) {
    pause mouse button1,keypress
    if (MOUSE_KEY == 27) { continue=0 }   # press ESC to stop loop
    else {
        x0 = MOUSE_X
        y0 = MOUSE_Y
        plot dMin=NaN \
            $Data u (d=dist(1,2), (dMin!=dMin || d<dMin)?(dMin=d,idxMin=$0):0,$1):2 skip 1 \
                w p pt 7 ps 2 lc "blue" ti "start point", \
            '' u (d=dist(3,4), d<dMin?(dMin=d,idxMin=$0):0,$3):4 w p pt 7 ps 2 lc "red"  ti "end point", \
            '' u 1:2:($3-$1):($4-$2) every ::idxMin::idxMin skip 1 w vec lw 2 lc black
    }
}
### end of script

Screen capture: (from wxt terminal)

enter image description here

Addition: (keep and remove arrows)

Here is a version where you keep several arrows and you can remove them by clicking again close to their start/endpoint. You can remove all arrows at once if you do a right mouse click (on my system this is MOUSE_BUTTON==3).

  • define a string arrowIdxscontaining the selected arrows, i.e. line indices
  • define a function inList() which checks if an index is in the list
  • define a function updateList() which either adds a new index or removes an index if it was already in the list.

I hope you can figure out yourself how the script works in detail. Check help <keyword> for the following used keywords: sprintf, sum, strstrt, strlen.

Script:

### show/hide arrows on mouse button click
reset session

$Data <<EOD
# first   # second
x1  y1    x2  y2
5   1     4   5
6   2     8   2
9   8     2   7
EOD

set offsets 1,1,1,1
set key out noautotitle

plot $Data u 1:2 w p pt 7 ps 2 lc "blue" ti "start point", \
        '' u 3:4 w p pt 7 ps 2 lc "red"  ti "end point"

dist(colX,colY) = sqrt((column(colX)-x0)**2 + (column(colY)-y0)**2)

arrowIdxs = ''
inList(list,n)     = (_s=sprintf("%d",n), int(sum[_i=1:words(list)] word(list,_i) eq _s))
updateList(list,n) = (_s=sprintf("%d",n), inList(list,n) ? \
      list[1:strstrt(list,_s)-2].list[strstrt(list,_s)+strlen(_s):] : list.' '.sprintf("%d",n))

continue = 1
while (continue) {
    pause mouse button1,button3,keypress
    if (MOUSE_KEY == 27) { continue=0 }   # press ESC to stop loop
    else {
        mb=MOUSE_BUTTON
        if (mb==1) {
            x0 = MOUSE_X
            y0 = MOUSE_Y
            plot dMin=NaN \
                $Data u (d=dist(1,2), (dMin!=dMin || d<dMin)?(dMin=d,idxMin=$0):0,$1):2 skip 1 \
                    w p pt 7 ps 2 lc "blue" ti "start point", \
                '' u (d=dist(3,4), d<dMin?(dMin=d,idxMin=$0):0,$3):4 w p pt 7 ps 2 lc "red"  ti "end point", \
                arrowIdxs = updateList(arrowIdxs,idxMin) \
                '' u (inList(arrowIdxs,$0)? $1:NaN):2:($3-$1):($4-$2) skip 1 w vec lw 2 lc black
        }
        if (mb==3) {
            arrowIdxs = ''
            plot $Data u 1:2 w p pt 7 ps 2 lc "blue" ti "start point", \
                    '' u 3:4 w p pt 7 ps 2 lc "red"  ti "end point"
        }
    }
}
### end of script

Screen capture: (from wxt terminal)

enter image description here

Upvotes: 2

Ethan
Ethan

Reputation: 15093

I think this is possible, almost. But you wouldn't use hypertext, you'd use the "toggle plot" functionality. I will show a complete script below, but here's the basic idea:

Draw a graph that contains (plot 1) the tail points (plot 2) the head points (plots 3 to N) a separate plot for each arrow. Because each arrow is in a separate plot it can be toggled on/off by clicking on its title. The toggle commands in the script turn the arrows off to start with. Now we get to the tricky part. Instead of letting the title be placed automatically in the key box for the graph, we explicitly position each title on top of its tail point. Now you can click on the point and voila, the arrow appears.

Here is the script

set xrange [0:10]
set yrange [0:10]
set style arrow 1 head nofill lc "black"

# Here is the data
$ARROWS << EOF
x1  y1  x2  y2    
5   1   4   5     
6   2   8   2     
9   8   2   7     
EOF

#
# This is the basic plot, with everything visible.
# In an interactive terminal (qt/wxt/x11/windows/svg) you can toggle
# the arrows on and off by clicking on the corresponding title in the key.
#

set key samplen 0.01

plot $ARROWS using 1:2 with points pt 7 ps 2 title "Click me", \
     $ARROWS using 3:4 with points pt 7 ps 2 notitle, \
     for [i=1:*] $ARROWS every 1::i::i using 1:2:($3-$1):($4-$2) with vectors as 1 title " "

pause -1

#
# Now we get tricky by placing the arrow titles on top of the points
# rather than in the key, and we programmatically toggle off the arrows.
# You should be able to make the arrow appear by clicking on the point
# that corresponds to the arrow tail.
#

array tx[3]
array ty[3]
do for [i=1:3] {
    tx[i] = int(word($ARROWS[i+1],1))
    ty[i] = int(word($ARROWS[i+1],2))
}

plot $ARROWS using 1:2 with points pt 7 ps 2 title "Click me", \
     $ARROWS using 3:4 with points pt 7 ps 2 notitle, \
     for [i=1:3] $ARROWS every 1::i::i using 1:2:($3-$1):($4-$2) with vectors as 1 \
        title " " at first tx[i], first ty[i]

do for [plot=3:5] {toggle plot}

pause -1

Here is a screen shot of the plot before the first click enter image description here

And here is a screen show of the plot after clicking on one of the points enter image description here

I say this is "almost" possible because I had to use a very clunky separate step to load the title coordinates into arrays. It would be much cleaner to generate them on the fly as the plot is drawn. I don't think that is possible currently, though maybe I am missing some clever trick.

Upvotes: 1

meuh
meuh

Reputation: 12255

Extending on my answer to your previous question, this is the sort of thing you can do with hypertext image:

screen snapshot

This is a screen snapshot of an svg file with the (yellow) cursor hovering above the point (4,5). The svg viewer (in this case, my browser) has popped up the small image with the purple arrow from the file /tmp/fig1.png on my system. It may not be exactly what you want, but it's the best I can do with gnuplot. Here's the code:

set term png size 400, 400   linewidth 3
unset key
stats "a_test.dat" nooutput
array xx[STATS_records]
array yy[STATS_records]
set xrange [STATS_min_x:STATS_max_x]
set yrange [STATS_min_y:STATS_max_y]
set offsets graph 0.05, graph 0.05, graph 0.05, graph 0.05
# save all data into two arrays
i = 1
fnset(x,y) = (xx[i]=x, yy[i]=y, i=i+1)
set table $dummy
 plot "" using (fnset($1,$2)) with table
unset table
numi = int((i-1)/2)

#-----create fig*.png files
do for [i=1:numi] {
  set output "/tmp/fig".i.".png"
  plot for [j=i:i] $dummy using (xx[j]):(yy[j]):(xx[numi+j]-xx[j]):(yy[numi+j]-yy[j]) with vectors
  set output
}

#-----2nd part

set term svg  size 400, 400  mouse standalone   linewidth 3
set output "aaa.svg"

indices = "first second"
count=0
fncount(x,y)=(count=(count==numi?1:count+1),count)
plot for [i=1:words(indices)] "a_test.dat" \
 index word( indices, i ) \
 u 1:2:(sprintf("image:/tmp/fig%d.png\nfig",fncount($1,$2) )) \
 w labels hypertext point pt 7 ps 2  notitle

I won't explain the lines that are from the previous answer, such as reading the data into the 2 arrays xx and yy. To keep the same scale for all the graphs, it is explicitly set using set xrange ... to min and max calculated from the statistics command.

The first part of the code uses do for to create files fig1.png and so on, each showing just one arrow, using one set of 4 data items from the arrays.

The second part selects the svg terminal type, with option mouse and standalone. This inserts the JavaScript needed to handle the mouse popup into the svg output file. The plot command is based on your example, with the 3rd column of data being, for example, the string "image:/tmp/fig1.png\nfig" for the first set of data. The function fncount() just increments on each call, from 1 to 3 (if you have 3 lines of data per block), then back to 1. This re-uses the same 3 image files at the start and end data points.

Note that the svg viewer must be able to find the image files in the filesystem.

Upvotes: 0

Related Questions