Sunday, 14 June 2009

False 3D bargraphs in gnuplot

Yesterday, we saw how cuboids representing bargraphs can easily be drawn in gnuplot. That implementation was completely 3D, so to speak. Today we will try something simpler. The idea is that in order to suggest that something is in 3D, it is not necessary to draw its 2D projection accurately, it is enough, if we can give the impression that the object is a three-dimensional one. Perhaps, the simplest way to do this is to colour the surface properly: by making some parts shiny, we can pretend that light is reflected from a curved surface. This is the trick that we are going to use today. First, we will take an easy example, and then later we can expand it. We will draw the following graph in a couple of lines:




As usual, we give the gnu script first, and then dissect it, step by step.
reset

f(x, a, b, c) = a*exp(-(x-b)*(x-b)/c/c)
A = 0.6
B = 0.5
C = 0.2

unset key
unset colorbox
unset xtics
set ytics 0,1,4
set parametric
set urange [0:0.5]
set vrange [0:1]

set pm3d map
set xrange [0:10]
set yrange [0:4]

set multiplot
set isosamples 100, 100
set palette model HSV function 2.0/3.0, 0.4-0.4*gray, (4.0+2.0*gray)/6.0
splot 20*u, 4*v, 2*u+v w pm3d

set isosamples 100, 2
set palette model HSV function 0.95, 1-f(gray, A, B, C), (1+2*f(gray, A, B, C))/3
splot u+1,v,u w pm3d,\
u+2,2*v,u w pm3d, \
u+3,3*v,u w pm3d, \
u+4,2*v,u w pm3d, \
u+5,3*v,u w pm3d, \
u+6,3.5*v,u w pm3d, \
u+7,1.4*v,u w pm3d, \
u+8,3*v,u w pm3d, \
u+9,1*v,u w pm3d

unset multiplot


First, we define the function according to which we are going to colour the bars. This is done by defining f(x,a,b,c) and the parameters A, B, and C. The the following couple of lines define the graph properties. The first relevant line is in blue, where we draw the background of our graph. We define the palette for pm3d using the HSV colour space. As in RGB, the colour is given as a triplet, the first member of which is the hue (in this case it will be something close to blue), the second is the saturation, which we defined to be whiter, if the value of gray is higher, and finally, the value. We then plot '20*u, 4*v, 2*u+v' on our map. By plotting the value 2*u+v, we will get a gradient that runs from bottom to top, left to right. You can modify this, if you want to change the direction in which the background becomes whiter.

Having drawn the background, we can plot the bars. This is done in the next step, where we always plot something like 'u+shift,value*v,u', where shift is the position of the bar, and value is its height. Note that before calling splot, we re-defined our palette, using the function f(x,a,b,c) from the beginning of the script. 'a' will set how white the colour becomes, 'b' is the position of the highest saturation value (or the centre of the pattern), and 'c' determines how tight the colour gradient is.

Now, let us see how this procedure can be automated. As previously, we will use a gawk script, which processes a data file with N+1 columns, the first setting the label, and the last N containing N values (i.e., instead of on set of bars, we will have N sets bars). Here is the script,

#!/bin/bash

gawk  'BEGIN {i=0; max=0}
 {
  if($0!~/#/) {
   label[i] = $1
   for(j=2;j<=NF;j++) {
         v[i,j-1] = $j
         if(max<v[i,j-1]) max=v[i,j-1]
   }
   i++
 }
 }
 END {
  print "reset"
  print "f(x, a, b, c) = a*exp(-(x-b)*(x-b)/c/c)"
  print "A = 0.6; B = 0.5; C = 0.2"
  print "unset key; unset colorbox; unset xtics; set pm3d map"
  printf "set yrange [0:%f]\n", max
  printf "set parametric; set urange [0:%f]; set vrange [0:1]\n", 1.0/(j-1)
  printf "set xrange [0:%d]\n", i+1
  print "set multiplot"
  print "set isosamples 100, 100"
  print "set palette model HSV functions 2.0/3.0, 0.4-0.4*gray, (4.0+2.0*gray)/6.0"
  printf "splot %d*u, %f*v, 2*u+v w pm3d\n", (i+1)*(j-1), max
  print "set isosamples 100, 2"
  for(l=1;l<j-1; l++) {
    if(l==j-2) {
   for(k=0;k<i;k++) {
         printf "set label %d \"%s\" at %f, -%f rotate by 45\n", k+1, label[k], k+0.5, max/5.0
  }
    }
    if(l>1) print "unset ytics; unset ylabel"
    printf "set palette model HSV function %f, 1.0-f(gray, A, B, C), (1.0+2.0*f(gray, A, B, C))/3.0\n", 1.0/l
    printf "splot "
    for(k=0;k<i;k++) {
         printf "u+%f,%f*v,u w pm3d,\\\n", k+l/(j-1), v[k,l]
    }
    printf "u+%f,%f*v,u w pm3d\n", i+l/j, v[i,l]
  }
  print "unset multiplot"
  
 }' $1


and here is the data file that I used to produce the figure below:
First 1.0 2.0 1.0
Second 1.0 1.1 3.2
Third 2.0 1.2 3.4
Fourth 1.2 2.4 1.1
Fifth 1.6 2.1 1.5



I believe, it should be fairly easy to hack the script to suit your needs. The points of interest are the colour of the background and the bars, and the position of the labels. Small tweaks in the script will give you just anything you would want. Next time I will come back to this script, and we will see how this can be modified to produce stacked bargraphs. So long!

5 comments:

  1. Hi,

    I came across your blog looking for a way to generate stacked bar charts with x-entries given as dates. I see you're planning to discuss how to stack the bars, so I'll wait patiently... Is this mechanism compatible with date values? Thanks for the blog, I got a number of ideas looking over your examples!

    ReplyDelete
  2. Hi,
    I was searching some commands in gnuplot and came to your blog.
    Can you tell me, how to draw inequalities in two dimension by gnuplot by directly giving the set of inequalities and the x-y range, so that it shades the required region.
    Regards, thanks and keep posting.

    ReplyDelete
  3. Hi Omri,

    I worked out the stacked graphs, and published them today. I am not sure whether this is what you would like to have.
    As for the date data, I think, you've got to sort that in advance. I believe, you could do this (if you are using linux) using the -M switch of sort, or you could also look at this page
    sorting by date
    Alternatively, you could check out the functions handling time in (g)awk. There is a chapter in Arnold Robbins' gawk manual titled 'Turning dates into timestamps', which should be applicable here. If you perhaps could give an example of your file, I would be glad to try my hand at it.
    I hope this helps.
    Cheers,

    ReplyDelete
  4. I have posted the solution to the question on how to plot inequalities. Let me know, if this is not what you had in mind.
    Cheers,
    gnuplotter

    ReplyDelete
  5. Regarding dates & sorting, I use perl, where it's easy enough to find modules which will handle all of this. My difficulty lies in combining the parametric splot commands you're using with gnuplot's timefmt, "set xdata time" mode, etc. But then again, I'm new to gnuplot so I might be missing something. Thanks again!

    - Omri

    ReplyDelete