Alfred's Guide to Making Beautiful and Complicated Figures with IDL

It's pretty simple to make a quick figure in IDL, but it doesn't typically look great. While it suffices for many situations, sometimes you just want to do a little better. Here's how I make complicated yet beautiful figures with IDL.

The first rule of making nice figures in IDL is: don't let IDL do anything by itself. I control all parameters of our figure myself. I should add as a disclaimer that a number of things described here are on the basis of what I find aesthetic. Your personal preferences might be different. The methods, however, are universally applicable.

I will refer to the figure as the entire product. The plots are only those areas that contain the objects that are to be displayed.

Output Device

There's really no point in making nice figures unless you're going to use them in some sort of document. Also, the pixel-oriented screen output is different in nature than the resolution-independent PostScript output that I want eventually. I therefore set the output to PostScript immediately:

set_plot, 'ps'

Font

By default, IDL will use an ugly, built-in font. The device fonts look a lot better, but they may change from one device to another. Even two printers may not have the same PostScript fonts. Therefore, I use the TrueType fonts:

!p.font = 1

Line Thickness

The lines IDL draws are typically very thin. I make them a bit thicker by setting some system variables:

!p.thick = 2
!x.thick = 2
!y.thick = 2
!z.thick = 2

Layout

I determine how the figure will be laid out. I decide how many plots it will have, how large they will be, and how they will be arranged. As an example, I'll cover the case of two plots of equal size, with an aspect ratio equal to the golden ratio. I'll assume the plots have the same x-axis, so I'll position them one above the other. In many cases where the figure is complicated, I prefer to make a sketch on paper. At this point, I also decide on the width of the figure. In this case I'll take 8.8 centimeter, because it exactly fits a column in most journals:

xsize = 8.8

Margins

I like to have the same distance from the bottom of my figure to the bottom horizontal axis as from the left edge to the left vertical axis. Trial and error have shown that 12% of a 8.8-centimeter wide figure is a good value:

margin = 0.12

I also have a fixed distance between two panels, the top of the figure to the top horizontal axis, and the right of the figure to the right vertical axis:

wall = 0.03

Figure and Plot Size

I can now compute the width of the plots in units normalized to an 8.8-centimeter wide figure:

a = xsize/8.8 - (margin + wall)

Since I settled on the golden ratio for the aspect ratio of the plots, I can compute the height of each plot:

b = a * 2d / (1 + sqrt(5))

Next, I compute the vertical size of the figure:

ysize = (margin + b + wall + b + wall)*8.8

Plot Positions

Now, I must compute the corners of the plots so I can instruct IDL to place them exactly where I want them. So far I've worked with units normalized to an 8.8-centimeter wide figure, but IDL can't do that. I'll calculate the horizontal and vertical coordinates in units normalized to the figure width and height, respectively:

x1 = margin*8.8/xsize
x2 = x1 + a*8.8/xsize
xc = x2 + wall*8.8/xsize
y1 = margin*8.8/ysize
y2 = y1 + b*8.8/ysize
y3 = y2 + wall*8.8/ysize
y4 = y3 + b*8.8/ysize
yc = y4 + wall*8.8/ysize

If I've done everything right, the values of xc and yc should now be equal to 1 (or, because of limited accuracy, very close).

Tick Marks

I also want the horizontal and vertical tick marks of the plots to have equal length. The manual is rather cryptic, but it turns out IDL scales the length of a vertical tick mark to the height of the plot, and the horizontal to the width. I keep my ticks the same length, irrespective of the size of the figure:

ticklen = 0.01
xticklen = ticklen/b
yticklen = ticklen/a

Plotting Data

I start by opening the output file. I want encapsulated PostScript output of a specific size. I also want to use the TrueType Times font, with a font size of 10 points:

device, filename='output.eps', /encapsulated, xsize=xsize, ysize=ysize, $
/tt_font, set_font='Times', font_size=10

If I was plotting an image, I would add the keyword bits_per_pixel=8 to make sure I get a sufficient number of shades of gray, and /color to enable colors.

Next, I'll create the frame of the first plot, using the position keyword to control where it will end up in the figure:

plot, xrange, yrange1, /nodata, /noerase, position=[x1,y1,x2,y2], $
xtitle='xtitle', ytitle='ytitle', $
xminor=1, yminor=1, xticklen=xticklen, yticklen=yticklen, $
xticks=nxticks, xtickv=xtickvalues, yticks=nyticks, ytickv=ytickvalues

Now, plot the data:

oplot, xaxis, yaxis1

Of course, you can add many more oplot or other statements here.

Once I'm done with the first plot, we proceed with the second plot. In this case, I don't want annotations on the x-axis:

plot, xrange, yrange2, /nodata, /noerase, position=[x1,y3,x2,y4], $
ytitle='ytitle', $
xminor=1, yminor=1, xticklen=xticklen, yticklen=yticklen, $
xticks=nxticks, xtickv=xtickvalues, yticks=nyticks, ytickv=ytickvalues, $
xtickname=replicate(' ',nxticks+1)

The only way to suppress the tick names is by setting them to spaces with the {x|y|z}tickname keyword. It's also possible, but perhaps less elegant, to use xtickname=replicate(' ',60). It appears that 60 is the maximum number of tick marks, so this should suppress tick labels in all cases. Now plot the data:

oplot, xaxis, yaxis2

Finishing Up

Finally, close the output file. Optionally, set the plot device back to the screen (XWindows output, in this case):

device, /close
set_plot, 'x'

Notes

IDL has a built-in tool !p.multi for making multi-panel plots. I've used this in the past, and though it does what it's supposed to, it's far from pleasant to work with. It's limited in its possibilities (e.g., it only allows for plots of equal size), and it changes things I don't want it to touch, such as the font size. The plot keyword noerase comes in handy. By setting it, plot does not erase the figure before it starts. You must take care yourself that your plots don't overlap, etc., but in return you can make your figure as complicated as you want.

If you're making a figure of an image, there are a few things you should take care of. First of all, the aspect ratio of the plot area is set by the image. Secondly, you should probably add bits_per_pixel=8 to the device call to ensure sufficient color depth. Finally, you should be careful with the order in which you draw the figure. The figure will be built up in layers, so if you want to plot something over the image (e.g., contours, etc.) you must tv the image first, then plot the contours. In most cases, I will tv the image before I draw the axes with the plot call. In some extreme cases, I have even drawn the axes last, by first using invisible axes.

Obviously, you don't want to type all this every time you make a small adjustment to this particular figure. I always write the code to produce a figure in an IDL procedure. The added advantage is that IDL will clean up after the procedure ends, removing all non-global variables (i.e., not !p.font etc. — I set those in my startup scripts).