graphgnuplothistogram

gnuplot: back arrow-style inconsistent with multiplot


I'm trying to use gnuplot 6.0 patchlevel 0 (as provided by Chocolatey under MS Windows 10) to draw some arrows behind a series of plots, but the results are inconsistent and baffling.

Here is some sample data that reproduces the behavior, saved as question-data.csv:

X,Y,Group
0,3,A
1,7,A
2,3,A
3,7,B
4,3,B
5,7,B
6,3,C
7,7,C
8,3,C

This script uses plot to draw the graphs, and does what I mean.

dataFile = "question-data.csv"
set datafile separator ','
set yrange [0:10]

# Filter by group.
yornothing(l,lc,y) = ((l eq lc)?y:NaN)

# Has to be done before plot!
set arrow 1 from graph 0, first 4 to graph 1, first 4 nohead back lc rgb "#C0C000" lw 2
set arrow 2 from graph 0, first 5 to graph 1, first 5 nohead back lc rgb "#00FF00" lw 2
set arrow 3 from graph 0, first 6 to graph 1, first 6 nohead back lc rgb "#C0C000" lw 2

plot dataFile every ::1 using 1:(yornothing(strcol(3),"A",$2)) with filledcurves above lc rgb "#0000FF" notitle, \
    dataFile every ::1 using 1:(yornothing(strcol(3),"B",$2)) with filledcurves above lc rgb "#FFA500" notitle, \
    dataFile every ::1 using 1:(yornothing(strcol(3),"C",$2)) with filledcurves above lc rgb "#0000FF" notitle

result of script using plot

It took a while to realize that arrows have to be drawn before any plots; until then, they silently failed. I was unable to find where that was documented.

Here's a script that tries to use multiplot to draw the graphs, but the arrows end up in front of the first two plots, and only end up behind the third one:

dataFile = "question-data.csv"
set datafile separator ','
set yrange [0:10]

# Filter by group.
yornothing(l,lc,y) = ((l eq lc)?y:NaN)

# Has to be done before plot!
set arrow 1 from graph 0, first 4 to graph 1, first 4 nohead back lc rgb "#C0C000" lw 2
set arrow 2 from graph 0, first 5 to graph 1, first 5 nohead back lc rgb "#00FF00" lw 2
set arrow 3 from graph 0, first 6 to graph 1, first 6 nohead back lc rgb "#C0C000" lw 2

set multiplot
plot dataFile every ::1 using 1:(yornothing(strcol(3),"A",$2)) with filledcurves above lc rgb "#0000FF" notitle
plot dataFile every ::1 using 1:(yornothing(strcol(3),"B",$2)) with filledcurves above lc rgb "#FFA500" notitle
plot dataFile every ::1 using 1:(yornothing(strcol(3),"C",$2)) with filledcurves above lc rgb "#0000FF" notitle
unset multiplot

result of script using multiplot

If I add a bogus 4th plot to the multiplot, the arrows now mysteriously draw in front of all graphs:

dataFile = "question-data.csv"
set datafile separator ','
set yrange [0:10]

# Filter by group.
yornothing(l,lc,y) = ((l eq lc)?y:NaN)

# Has to be done before plot!
set arrow 1 from graph 0, first 4 to graph 1, first 4 nohead back lc rgb "#C0C000" lw 2
set arrow 2 from graph 0, first 5 to graph 1, first 5 nohead back lc rgb "#00FF00" lw 2
set arrow 3 from graph 0, first 6 to graph 1, first 6 nohead back lc rgb "#C0C000" lw 2

set multiplot
plot dataFile every ::1 using 1:(yornothing(strcol(3),"A",$2)) with filledcurves above lc rgb "#0000FF" notitle
plot dataFile every ::1 using 1:(yornothing(strcol(3),"B",$2)) with filledcurves above lc rgb "#FFA500" notitle
plot dataFile every ::1 using 1:(yornothing(strcol(3),"C",$2)) with filledcurves above lc rgb "#0000FF" notitle
# Empty plot, to change nature of problem
plot dataFile every ::1 using 1:(yornothing(strcol(3),"D",$2)) with filledcurves above lc rgb "#0000FF" notitle
unset multiplot

result of hacked script using multiplot

Is this a bug, or my misunderstanding of multiplot? gnuplot is reasonably complex, and I have only scratched its surface.


Solution

  • I think this is a misunderstanding of multiplot. "set multiplot" allows you to issue multiple plot commands that draw onto the same canvas without erasing the canvas in between plots. Each of the plot commands in the multiplot sequence is drawn in full using all of the settings that are in effect at the time of that plot command, including all of the objects, arrows, labels, etc that are defined. So your three arrows are drawn all over again for each of the three plot commands inside the multiplot, and each time they are drawn they necessarily are drawn on top of the previous content of the canvas (since it isn't erased in between).

    As to your first remark that "arrows have to be drawn before any plots", that may indicate a separate misunderstanding. The set arrow command does not by itself drawn an arrow. It only defines an arrow that will be drawn as part of all subsequent plot commands. This is the same as for the commands set label, set object, and so on that define additional elements that will be included in all subsequent plots.

    One more thing that may be relevant. When you define your arrows you can specify whether they appear behind or in front of the curves drawn by plot with filledcurves. The back keyword in your set arrow commands tells the program that the arrows should be in back of the curves; front would instead draw them in front. However as explained above, in the case of multiplots the front elements of a component plot command are nevertheless overwritten by everything in the subsequent plot commands.

    gnuplot> help layer
           
     A gnuplot plot is built up by drawing its various components in a fixed order.
     This order can be modified by assigning some components to a specific layer
     using the keywords `behind`, `back`, or `front`. For example, to replace the
     background color of the plot area you could define a colored rectangle with the
     attribute `behind`.
          set object 1 rectangle from graph 0,0 to graph 1,1 fc rgb "gray" behind
     The order of drawing is
          behind
          back
          the plot itself
          the plot legend (`key`)
          front
     Within each layer elements are drawn in the order
          grid, axis, and border elements
          pixmaps in numerical order
          objects (rectangles, circles, ellipses, polygons) in numerical order
          labels in numerical order
          arrows in numerical order
     In the case of multiple plots on a single page (multiplot mode) this order
     applies separately to each component plot, not to the multiplot as a whole.
    

    Illustration

    Here is a slight modification of your multiplot example that makes it easier to see what is happening. It shifts the origin after each invocation of plot inside the multiplot so that redrawn elements are slightly displaced from their previous locations. This makes it clear that the arrows are being drawn multiple times. Arrow colors are changed to make them easier to distinguish from the filled curves.

    # Has to be done before plot!
    set arrow 1 from graph 0, first 4 to graph 1, first 4 nohead back lc rgb "yellow" lw 2
    set arrow 2 from graph 0, first 5 to graph 1, first 5 nohead back lc rgb "brown" lw 2
    set arrow 3 from graph 0, first 6 to graph 1, first 6 nohead back lc rgb "dark-green" lw 2
    
    set obj rect from graph 0,0 to graph 1,1 fc "gray" fs transparent solid 0.25
    
    set multiplot
      plot dataFile every ::1 using 1:(yornothing(strcol(3),"A",$2)) with filledcurves above lc rgb "#0000FF" notitle
        set origin 0.05, 0.05
      plot dataFile every ::1 using 1:(yornothing(strcol(3),"B",$2)) with filledcurves above lc rgb "#FFA500" notitle
        set origin 0.1, 0.1
      plot dataFile every ::1 using 1:(yornothing(strcol(3),"C",$2)) with filledcurves above lc rgb "#0000FF" notitle
    unset multiplot
    

    enter image description here