Wednesday, 29 July 2009

Adding shadow to the key

I think, it changes the impression a graph can make by quite a lot, if it looks sort of three-dimensional. Even a tiny twist to the boring 2D lookout can make a difference, though it doesn't give any new information to the reader. However, in a presentation, it is almost expected that one produces "exciting" graphs. Today, we will discuss an easy way to add a slight shadow to the key on a graph, as if it were lifted from the plane of the curves. At the end of our exercise, we will have the following figure

I believe the steps are quite straightforward, so I won't spend too much time on explaining every detail. Here is the script that we need


xl=0; xh=1; yl=-1; yh=1;
rx=0.6; ry=0.8; kw=0.35; kh=0.15
lh=0.06; al=0.1

key1="First function"
key2="Second function"

set table 'shadowkey.dat'
splot [xl:xh] [yl:yh] x/(xh-xl)
unset table

set object 1 rect from graph rx,ry rto kw,kh fc rgb "#aaaaaa" fs solid 1.0 front lw 0
set object 2 rect from graph rx-eps,ry+eps rto kw,kh front fs empty
set label 1 at graph 1.1*al+rx, ry+2*lh key1 front
set label 2 at graph 1.1*al+rx, ry+lh key2 front
set arrow from graph rx, ry+2*lh rto al, 0 lt 1 lw 1.5 nohead front
set arrow from graph rx, ry+lh rto al, 0 lt 3 lw 1.5 nohead front

unset colorbox
unset key
set palette defined (0 "#8888ff", 1 "#ffffff")
plot [xl:xh] [yl:yh] 'shadowkey.dat' w ima, \
x*x*exp(-x) lw 1.5, cos(13*x)*exp(-3*x) lt 3 lw 1.5

I collected all variables at the beginning of the script, so that it will be easier to adapt it to any situations. The first four numbers define the xrange and the yrange. The next four numbers specify the position and the size of the key. We will have to make our own key, and the size of the key will depend on the particular terminal one uses. This is why it is handy to define them at the beginning of the script, so if you are not satisfied with the results, you can easily adjust both the position and the size. The next number (lh) gives the hight of one key line, while 'al' will be the length of the line which represents the curve. 'key1' and 'key2' are just two arbitrary strings, holding the text of the key.

The next three lines are necessary only if you want to draw the background gradient, but if you are happy with a white plot, you can skip this. I should also point out that the first four numbers that are needed for the xrange and yrange, are needed only because we have to "synchronise" the plot with its background. If you don't want the gradient, you can drop these definitions, and you haven't got to specify the range in the plot either. This might be useful, when you don't know the plotting range beforehand, and want to let gnuplot calculate it for you.

The next step is to draw two rectangles in the front of the graph: one gray, and one white. Note that the rectangle in gray has no boundary, i.e., that is set to zero width. Also note that we specify the coordinates in terms of the numbers we defined at the beginning, so both rectangles will change accordingly, if you change those numbers. This means that the white rectangle and its shadow will always be linked, given by the variable 'eps'. When we are done with the white rectangle, we can place the text of the keys, and the two lines representing our plots. For this purpose we draw two arrows without heads, and with the linetypes that we will use for plotting the curves.

The final step is to actually draw the curves. Before that, we plot our file, 'shadowkey.dat', so the graph will have a background gradient. If you skipped those three lines writing the data file to disc, you should replace the plotting command by

plot [xl:xh] [yl:yh] x*x*exp(-x) lw 1.5, cos(13*x)*exp(-3*x) lt 3 lw 1.5

It should go without saying that in this case, you can also drop the palette definition, for it will not be used. Well, this is it for today. I know that this was a simple trick, but we can't have something complicated every day!


  1. As you can see, I am still hanging out in the simple parts of your blog. Very nice.

    I suppose in the example script you left the key area empty intentionally to make the reader see what's going on? It's easily fixed, of course, so a good learning experience.

    Is there a good way, to use the graph or screen coordinate systems to make things predictable (when the plot does not span [0:1][0:1])?

    What is a position / good ratio y vs x epsilon to make the shadow be as much down as sideways, depending on what rmarging, bmargin etc are?

  2. In the ps and eps terminals, I get a very thin black line around the gray shadow in gnuplot 4.4 (in spite of lw 0). Do you see the same? I have tried set style line # and ls # but that is not allowed as an option to an object. :(

  3. I've deleted a few posts because I've been basically talking to myself here, and no-one wants to know the several iterations I went through for this.

    There is an undocumented (in the built-in help) option "noborder" that can be prepended in front of the "front" attribute. [Also, border ls N lw M sets line style N for the border, and lw 0 is still ignored - gnuplot bug?]

    I made a version with a real key, not one drawn by hand.

    We can draw the rectangles as "behind" since the graph background is drawn separately.

    I have tried to extract gnuplot's guess for character dimensions and scaled the key area accordingly. Try changing the font size and figure dimensions (first three variables in my script) to see what I mean.

    #!/usr/bin/gnuplot -persist

    set term postscript enhanced eps color dashed size pwid,phgt "Helvetica" pfnt
    set output "tricks-bkey.eps"

    xl=0; xh=20; yl=-1; yh=1;

    set xrange [xl:xh]
    set yrange [yl:yh]

    ## tic length, character width, character height (gnuplot's estimate)
    ## (reverse engineered for term postscript eps Helvetica)

    ## key position, epsilon for shadow, width and height
    kmag=0.05*pfnt/24; kx=1-rmar*chrw-kmag*phgt/pwid; ky=1-tmar*chrh-kmag/2
    yeps=kmag/2; xeps=yeps*phgt/pwid;
    ## adapt these three to your key items
    delwidth=4; sampl=2;

    keys2='Data set 2'

    keylen=max(keyl1, keyl2)


    f(x) = exp(-x/10.0)*sin(x)
    g(x) = exp(-x/10.0)
    h(x) = -exp(-x/10.0)

    set sample 30
    set table 'test.dat'
    plot f(x)+(rand(0)-0.5)*0.2
    unset table

    set sample 128
    set isosamples 2,128

    set table 'background.dat'
    splot (y-yl)/(yh-yl)
    unset table

    set multiplot

    set rmargin 0
    set lmargin 0
    set tmargin 0
    set bmargin 0

    unset colorbox
    unset border
    unset xtics
    unset ytics

    set palette defined (0 "#68c068", 1 "#ffffff")
    plot 'background.dat' w ima

    set rmargin rmar
    set lmargin lmar
    set tmargin tmar
    set bmargin bmar

    set palette defined (0 "#e68080", 1 "#ffffff")
    plot 'background.dat' w ima

    set key at screen kx,ky reverse Left samplen sampl width -delwidth box
    set object 1 rect from screen kx+xeps,ky-yeps rto screen -kw,-kh fc rgb "#aaaaaa" fs solid 1.0 noborder behind
    set object 2 rect from screen kx,ky rto screen -kw,-kh fc rgb "white" fs solid 1.0 noborder behind

    set xtics
    set ytics
    set xlabel 'Time [s]' offset 0,xofs
    set ylabel 'Position [m]' offset yofs,0
    set border 1+2+4+8

    plot \
    f(x) w l lt 3 lw 2 t keys1 \
    , g(x) w l lt rgb "#008800" t '' \
    , h(x) w l lt rgb "#008800" t '' \
    , 'test.dat' u 1:2:($2-rand(0)*0.1-0.05):($2+rand(0)*0.1+0.05) t keys2 w errorb pt 13 lt 1 \

    unset multiplot

    !/bin/rm -f test.dat background.dat
    !gv -scale=3 tricks-bkey.eps &