IDL plot objects
This document gives some notes about using IDL plot objects.
Particular focus is given on how to reproduce features from IDL direct
graphics.
If you want to test your plot object expertise, then try my exercises.
I also have a list of IDL
plot object quirks that you should be careful of.
What is a plot object made of?
When you create a plot object with, for example,
IDL> p=plot(findgen(5))
it's worth noting that 'p' is really just the straight line
corresponding to the data. In putting this on the screen, however, IDL
has had to first create a window that contains the straight line plot,
and create four axes. Each of these is a plot object in its own right.
The objects can be accessed by doing:
IDL> window=p.window
IDL> axes=p.axes
So if you'd like to change the background color from white to pink, and change the plot dimensions to 700x400, you can do:
IDL> window.background_color='pink'
IDL> window.dimension=[700,400]
Similarly you can modify the axis properties - see "Suppressing or replacing plot axes" further down.
If you now overplot a new object:
IDL> q=plot(/overplot,4-findgen(5),color='blue')
then 'q' inherits the window and axes objects (thus q.window
and q.axes are the same as p.window and p.axes). One consequence is
that if you try to close 'q', thinking that it will only remove the
over-plotted line, you find that the entire window gets destroyed. This
is because 'close' actually tells IDL to close the window that the object is plotted in, not just the object.
This behavior also helps explain what happens when you do:
IDL> p=plot(findgen(5))
IDL> p=plot(4-findgen(5),color='blue',/overplot)
IDL> p=plot([0,4],[2,2],color='green',/overplot)
Why does IDL not get confused by having three objects of the same name?
Because each new 'p' inherits the window and axes from the previous
'p'. You can no longer modify the first 'p' once you've created the
second one, but you can modify 'window' and 'axes' since they've just
been copied into the new 'p'.
Multiple plots on one page
IDL> p1=plot(findgen(5),layout=[2,1,1])
IDL> p2=plot(findgen(5),layout=[2,1,2],/current)
The POSITION= keyword is also available to directly position a
plot
within a window.
When annotating multiple plots with the 'text' object (the
equivalent of xyouts in direct graphics), make sure to specify which of
the existing plot objects is being annotated by using the target= input:
IDL> p1_text=text(1,1,/data,'Hello',target=p1)
IDL> p2_text=text(1,1,/data,'Goodbye',target=p2)
Creating an output file with fixed size
When creating a .eps file with direct graphics, the X-size and
Y-size are set with a call to DEVICE. With object graphics, the plot
window that is displayed to the screen can be manually adjusted using
the mouse, and there's a button in the plot window that allows it to be
saved in various formats. For figures for journals, however, you want
to have an IDL routine that generates a plot with a fixed size without
any manual intervention.
For publications I always use png or jpeg format for my
figures. If the figure contains an image then you should use
jpeg, while if it contains lines or symbols then you should use
png.
My procedure for creating a plot for a publication is to do,
e.g.,
xdim=600
ydim=450
p=plot(findgen(5),dimension=[xdim,ydim])
p.save,'idl.png',width=2*xdim
p.close
Your output image will have dimensions twice that of the IDL
plot window, which I find gives a resolution suitable for
journals.
Encapsulated postscript files
These used to popular for publications, but they aren't
handled by pdflatex so it is best not to use them. However, to
create an eps file from IDL is simple. Following the above
example, you just do:
p.save,'idl.eps'
Images with axes
With
direct graphics there wasn't a built-in routine to plot 2D images with
X and Y axes, so many people used the Solarsoft routine 'plot_image'.
Using the object function image.pro it's easy to produce image plots
with axes. One point to note is that plot_image plotted images such
that the coordinates corresponded to the center of the pixels. With
image.pro, they correspond to the bottom-left corners of the pixels so
care has to be taken when comparing the two routines.
The functionality of plot_image can be reproduced using the
IMAGE
object. The following example assumes that 'a' has size 256x256:
IDL> x=findgen(256)-128
IDL> y=findgen(256)-128
IDL> p=image(a,x,y,axis_style=2)
The equivalent of the SCALE= input to plot_image is achieved
by
simply multiplying X and Y by the scale factors. For example:
IDL> x=(findgen(256)-256)*2
IDL> y=findgen(256)-128
IDL> p=image(a,x,y,axis_style=2)
will stretch the image in the X-direction by a factor 2.
By default, image() will plot the image at its exact size - in
this
case 256x256 pixels. To make the image fill the display window better,
the best option seems to be with the MARGIN= keyword. For example:
IDL> p=image(a,x,y,axis_style,margin=0.10)
Problem with scaling
Even after using the margin keyword, you may want to stretch
the image in one direction in order to make a better plot. You can do
this with, e.g.,
IDL> p.scale, 1, 1.5
which applies a 1.5 scaling in the Y-direction.
This works fine if the x and y arrays that define the axes are
uniformly spaced. If they're not uniformly spaced, then the software
seems to apply an interpolation to the data when doing the scaling. You
will notice this by the fact that the pixels will not be stretched. I
find that the interpolation often introduces ugly artifacts.
If your axes are close to being uniformly spaced, then I
suggest re-defining them to make them uniformly-space and then using
these new axes.
One example where I've seen this effect is when time is one
axis of an image and (typically with space image data) the time
difference between each frameis not exactly the same.
Suppressing or replacing plot axes (the axis object)
When you create a plot, it's worth noting that IDL
creates the plot (a line plot, symbol plot, image, etc.) and a set of
axes. Each axis is it's own object. Thus if you do:
IDL> p=plot(findgen(5))
'p'
is really just the straight line representing the data, but IDL has had
to create a set of four axis objects representing each of four
displayed axes. You can access these axes by doing
IDL> ax=p.axes
'ax' is an object array that specifes each of the four axes. ax[0] is
the bottom X-axis, ax[1] is the left Y-axis, ax[2] is the top X-axis
and ax[3] is the right Y-axis. You can make any of the axes disappear
by using the 'hide' property. For example:
IDL> ax[3].hide=1
gets rid of the right Y-axis. Set hide to 0 to make it reappear.
You can also modify the individual axes separately. For example:
IDL> ax[3].thick=3
IDL> ax[3].color='blue'
You can also perform a coordinate transform to the axis, which is
useful if you're using the two Y-axes to represent two different
curves. Suppose the left axis represents degrees centigrade, then the
right axis can represent by Fahrenheit by doing
IDL> ax[3].coord_transform=[32,9./5.]
(You may have to do ax[3].showtext=1 to show the axis numbers.)
Colors
For line and symbol plots, specifying colors is much easier
than with direct graphics. For example:
IDL> p=plot(findgen(5), color='red')
Note that only the data will be colored red, with the axes
left with their default colors
(use xcolor and ycolor to change the axis colors). See the IDL user
guide for a complete set of IDL color names, and alternative ways of
specifying the color.
For images, you can specify the standard IDL color tables as
follows:
IDL> p=image(data, rgb_table=n)
where n is the number of table (e.g., 3 for the standard red
table).
If you download
my idl_misc
GitHub repository, then you can
make use of the Python matplotlib color tables. For example:
IDL> p=image(data,
rgb_table=matplotlib_rgb_table(/viridis))
Use the /help keyword for matplotlib_rgb_table to see more
options.
If you download my sdo-idl
GitHub repository, then you can make use of the standard
AIA color tables. For example:
IDL> p=image(data,
rgb_table=aia_rgb_table(193))
I like the 193 table and often use it for other types of data.
Spectrum plot (histogram)
Spectra are usually displayed with a histogram plot, and with
direct
graphics one uses PSYM=10. With the plot object command, the correct
option is /stairstep:
IDL> p=plot(x,y,/stairstep)
This option means that the bars of the plot are centered on
the X-values.
There is also the /histogram plot option, which means the
left sides of the bars are placed at the X-values. In
particular, this should be used when you use the IDL histogram
function. For example:
IDL> y=histogram(data,min=0,max=100,bin=5,loc=x)
IDL> p=plot(x,y,/histogram)
but don't use this for spectra!
Object-equivalent for UTPLOT
The SSW routine UTPLOT is widely used for plotting light
curves, giving
time in a nice format on the X-axis. IDL has its own capability for
plotting times, but the times must be in Julian Day format.
Suppose we have a 1D array called TT containing times in any
of the standard SSW formats, and an array LC containing the light curve
data points. We can plot this with:
IDL> jd=anytim2jd(tt)
IDL> tt_jd=jd.int+jd.frac
IDL> p=plot(tt_jd, lc, xtickunits='time')
where
IDL will choose which time unit (seconds, minutes, hours, etc.) is best
to use based on the data range. You can force it to use hours by using
xtickunits='hours'.
The above would give the tick marks as, e.g., '0', '1', etc.,
for hours. If you'd prefer to have '00:00', '01:00', then you have to
specify a format with XTICKFORMAT='(C(CHI2.2,":",CMI2.2))'. Here C()
tells IDL to interpret the input as time, then CHI extracts the hour
portion, CMI the minute portion, and 2.2 gives the format for the
number string. Check the IDL help page for TICKFORMAT for more details.
If you want to set the X-range, you have to set it in Julian
Days. So use ANYTIM2JD again to convert your times, and then use
XRANGE=.
Application to GOES data
There is object-based software for plotting GOES data - see
the page
at GSFC. If you want to take the GOES data and plot it with the IDL
object software, then follow the method below.
First extract the GOES data for the required time period
t0=anytim2utc('4-mar-2014 12:45',/ccsds)
t1=anytim2utc('4-mar-2014 13:30',/ccsds)
g=ogoes()
g->set,tstart=t0,tend=t1,/sdac,mode=1
gdata=g->getdata(/struct)
obj_destroy,g
Create the time array in JD format:
jd=anytim2jd(gdata.utbase)
tref=jd.int+jd.frac
tdays=gdata.tarray/86400d
tt_jd=tref+tdays
Extract the light curve for the low energy channel, and then
plot:
lc=gdata.ydata[*,0]
p=plot(tt_jd,lc,xtickunits='time',xtickformat='(C(CHI2.2,":",CMI2.2))',xsty=1)
IDL Maps
Dominc Zarro created a suite
of software
for displaying solar images based around "maps", which are IDL
structures containing an image with metadata. I've created a routine
called plot_map_obj (available in Solarsoft) that allows a map to be
plotted as an object:
IDL> p=plot_map_obj(map)
I've tried to re-create the functionality of plot_map.pro, so
some of the keywords are the same:
/log - plot logarithm of the image
dmin - specify a minimum intensity value to display
dmax - specify a maximum intensity value to display
One new keyword is /velocity
which tells the routine that it's displaying a velocity map and so it
uses the familiar red/blue color table. Note that the keyword dmax= specifies the maximum absolute
velocity to display.
For over-plotting a map as a contour on another map, do:
IDL> p=plot_map_obj(map1)
IDL> q=plot_map_obj(map2, /overplot [, levels=levels])
where the keyword levels
allows the intensity levels of the contour to be plotted.
Color bars for image plots
These are straightforward to add, as illustrated below using a
map image:
IDL> p=plot_map_obj(map,/log)
IDL> pcb=colorbar(target=p,orientation=1,title='Log!d10!n
Intensity',/border_on)
By specifying the target as the image object, then colorbar
automatically picks up the range of intensity values in the image. The
orientation keyword determines whether the colorbar appears above the
image (0) or to the right side (1). The position keyword can be used to
place the colorbar at any location.
Creating a multi-page postscript/pdf file
If
you have a lot of data that you'd like to browse quickly,
it's useful to send the plots to a single postscript or pdf file, such
that single (or multiple) plots appear on each page. You can then
quickly flick through the pages (either with an electronic or
hardcopy).
This can be done using the /APPEND keyword when you save a plot. I give
an example below of making a 6-page pdf document with 2x3 plots per
page. Note that object "w" gets destroyed and recreated on each "i"
loop, but the channel that sends w to the pdf file remains open,
irrespective of this.
FOR i=0,5 DO BEGIN
w=window(dimension=[700,1000],/buffer)
FOR j=0,5 DO BEGIN
p=plot(randomu((i+1)*(j+1)*systime(1),5), $
yrange=[0,1],xrange=[0,4],/xsty,/ysty, $
layout=[2,3,j+1],/current, $
margin=0.05)
ENDFOR
w.save,'output.pdf',/append
ENDFOR
w.close
END
Destroying a plot object
Simply use the 'close' method:
IDL> p=plot(findgen(5))
IDL> p.close
Page maintained by Dr Peter R Young.
|