Usually, you want to leave a little buffer when specifying a plot
range so that your plot symbols aren't cut off. The way to do this is to
set the X/YSTYLE keyword in such a way that the following bits are set:
1 : Force exact axis range.
2 : Extend axis range.
So you would set XSTYLE=3. However, for the Y axis, you also want to
set the 16 BIT which tells IDL to prevent the Y axis minimum from being
forced to be zero. So in general, a nice looking plot can be made by
setting:
XSTYLE=3, YSTYLE=19
If you want to see the actual code for a routine right in your IDL
window, you can just use ".run -t"... for instance, say you want to see the
guts of profiles.pro without hunting it down in some directory:
IDL> .run -t profiles
Julia Kregenow was the only person on the planet who knew this.
You can store a bunch of code in a text file without a procedure or
function declaration. This can then be run by IDL and allows you to have
the functionality of a function or procedure but allows you to have command
line access to the variables that are defined at the MAIN level. These
types of programs are called "main-level programs" (also, "main programs"
or "$MAIN$ programs"). They require an END statement and, unlike BATCH
files, they do not need loops protected by &$.
(IDL refers to these as $MAIN$ programs because it compiles the code into a
program named $MAIN$ and executes it. So there.)
Now, as with BATCH files, you want to save your main program to a filename
ending with .pro but this is going to cause all sorts of confusion when you
end up having batch files and main programs interspersed with real
routines. Carl's solution is to name main programs with the suffix .idlprc
(I don't recall his reasoning for this shorthand) and batch files with the
suffix .idl. This makes life tricky for two reasons: (1) assuming your
text editor has an IDL mode, you have to make it recognize .idl and .idlprc
files as IDL files; (2) IDL adds directories to the path only if they
contains .sav or .pro files, so a directory full of batch files wouldn't be
added. I think Carl's started to adopt the habit of adding a .pro after
the .idl or .idlprc.
IDL has a documentation template here:
$IDL_PATH/examples/template.pro. The suggested format of the calling
sequence is:
Result = ALL_CAPS(First_Caps [, KEYWORD=all_caps] [, /SET])
Turns out .compile, .run, .rnew all check the current directory for
the module you're trying to compile before searching the rest of your IDL
!path variable. Same goes for DOC_LIBRARY. So if you stuck a "."
somewhere in your !path but not at the head of your !path, you're out of
luck when compiling... the current directory still gets searched
first. This is why I automatically add the current directory when searching
the IDL path in my procedure WHICH.PRO.
This is some pertinent useless information when dealing with plots on
X windows. I'll never ever figure this out again, so I thought I'd make a
note of it for posterity. If you draw a ridiculously thick line, you stand
the chance of overwriting your ticks and axes. Here's a quick example to
illustrate:
IDL> plot, findgen(10)
IDL> oplot, 10*[1,1], [0,10], thick=8, lines=2, color=!red
It seems that at some point I figured out that the pixel positions relative
to the center of the line for the edges of the thick line are given by:
[-1,1]*thick/2+[0,thick mod 2 eq 1]
So that the edges of a line in data units, if th=floor(thick), are:
xthick = (th gt 1) ? ([-1,1]*th/2+[0,th mod 2 eq 1]) / !x.s[1] / !d.x_vsize : 0
ythick = (th gt 1) ? ([-1,1]*th/2+[0,th mod 2 eq 1]) / !y.s[1] / !d.y_vsize : 0
So you can include the following keywords when using plots to draw lines
in your plot:
NOCLIP=0
CLIP=([xrange+xthick[[1,0]],yrange+ythick[[1,0]]])[[0,2,1,3]]
Installing IDL on my Debian laptop was a pain because the IDL install
software assumes Redhat Linux. Lame. So here's what needed changing.
First, change the lmgrd_install script in your /usr/local/rsi/idl/bin
directory (or wherever the hell you've put it... this is the default
location). Go down to the "Linux") option in the case statement and
comment out the junk they've put there and stick the stuff below in...
# THIS ASSUMES WE'RE USING RED HAT...
# RED HAT RUN LEVELS ARE STORED IN /etc/rc.d/rc[0-6].d/
# DEBIAN RUN LEVELS ARE STORED IN /etc/rc[0-6].d/
#cp sys5_idl_lmgrd /etc/rc.d/init.d
cp sys5_idl_lmgrd /etc/init.d
# if [ -f /etc/rc3.d/S99sys5_idl_lmgrd ]; then
# rm -f /etc/rc3.d/S99sys5_idl_lmgrd
# fi
# if [ -f /etc/rc4.d/S99sys5_idl_lmgrd ]; then
# rm -f /etc/rc4.d/S99sys5_idl_lmgrd
# fi
# if [ -f /etc/rc5.d/S99sys5_idl_lmgrd ]; then
# rm -f /etc/rc5.d/S99sys5_idl_lmgrd
# fi
# if [ -f /etc/rc0.d/K01sys5_idl_lmgrd ]; then
# rm -f /etc/rc0.d/K01sys5_idl_lmgrd
# fi
# ln -s /etc/rc.d/init.d/sys5_idl_lmgrd /etc/rc3.d/S99sys5_idl_lmgrd
# ln -s /etc/rc.d/init.d/sys5_idl_lmgrd /etc/rc4.d/S99sys5_idl_lmgrd
# ln -s /etc/rc.d/init.d/sys5_idl_lmgrd /etc/rc5.d/S99sys5_idl_lmgrd
# ln -s /etc/rc.d/init.d/sys5_idl_lmgrd /etc/rc0.d/K01sys5_idl_lmgrd
ln -sf /etc/init.d/sys5_idl_lmgrd /etc/rc3.d/S99sys5_idl_lmgrd
ln -sf /etc/init.d/sys5_idl_lmgrd /etc/rc4.d/S99sys5_idl_lmgrd
ln -sf /etc/init.d/sys5_idl_lmgrd /etc/rc5.d/S99sys5_idl_lmgrd
ln -sf /etc/init.d/sys5_idl_lmgrd /etc/rc0.d/K01sys5_idl_lmgrd
# I'M GOING TO ADD ONE TO RUN LEVEL 2 TO SEE WHAT HAPPENS...
# AND THIS IS WHAT IT NEEDED... HOURS WASTED...
ln -sf /etc/init.d/sys5_idl_lmgrd /etc/rc2.d/S99sys5_idl_lmgrd
OK, next you need to alter the sys5_idl_lmgrd script in the same directory. Go
down to the "start") option of the case statement and, as above, comment out and
add the stuff as below:
# WE DON'T WANT TO RUN THIS AS ROOT... IT'S DANGEROUS...
# "start") $IDL_DIR/bin/lmgrd >> $LOG_FILE_NAME &
"start") su <user name here> -c "umask 022;$IDL_DIR/bin/lmgrd &"
sleep 5 ;;
Now we can test this by running:
> sudo sys5_idl_lmgrd start
and now try running IDL and see if the license is recognized.
I had to (as root) create a subdirectory /usr/tmp which I just linked to
/var/tmp...
> sudo ln -s /var/tmp /usr/tmp
Now, it's good to go.
June 2006: One more hitch I discovered... to get the IDL assistant to work
correctly, I needed to give the user ownership of the directories
~/.assistant and ~/.qt (and the contents of these directories as well).
This fixed my problems.
I usually check for equality between arrays by doing something like so:
if (total(a ne b) eq 0) then
But there is a faster way that stops the comparison once an inequality is found,
which is useful for huge datasets:
if array_equal(a ne b, 0B)
Here's something that confused the hell out of me for about 3 minutes.
Take a really big unsigned 64-bit long integer:
IDL> help, 8446744073709551113ull
<Expression> ULONG64 = 8446744073709551113
Now let's convert it to a double, which can represent any number between
+/-10^(308); shouldn't be a problem since our number is only 8x10^(18), right:
IDL> print, float(18446744073709551113ull), form='(f25.1)'
18446744073709551616.0
Wrong. Deal is that in double-precision we only have about 16 significant
figures:
IDL> help, (machar(/double)).eps
<Expression> DOUBLE = 2.2204460e-16
So, everything makes sense here:
8446744073709551113 (REAL NUMBER)
| 10^16
8446744073709551616 (BAD CONVERSION)
That's just the limit of how well we can represent a double.
This was tough to find when I finally needed to... DATA TYPES:
BYTE: 8-bit unsigned [0 => 255b]
INTEGER: 16-bit signed [-32,768 => +32,767] (FIX)
LONG: 32-bit signed [-2,147,483,648l => +2,147,483,647l]
LONG64: 64-bit signed [-9,223,372,036,854,775,808ll => +9,223,372,036,854,775,807ll]
FLOAT: 32-bit single precision [+/-10^(38), 6 or 7 significant figures]
DOUBLE: 64-bit double precision [+/-10^(308)d, 14 or 15 significant figs]
COMPLEX: real/imaginary pair of floats
DOUBLE COMPLEX: real/imaginary pair of doubles (DCOMPLEX)
UINT: 16-bit unsigned [0 => 65,535u]
ULONG: 32-bit unsigned [0 => 4,294,967,295ul]
ULONG64: 64-bit unsigned [0 => 18,446,744,073,709,551,615ull]
Here's how to make a nice animation in IDL using pixmaps. You can't simply put
TV or PLOT calls in a loop... if you do, you'll just watch the lines being
displayed on a row-by-row basis... usually you'll loop faster than it takes to
actually finish drawing each plot. The trick is draw the plot to
identically-sized windows that are stored in memory, then cycle through these
and dump the contents into the window on your screen. The advantage is that the
entire contents are dumped simultaneously into the window, making for a true
frame-by-frame animation.
; OPEN A WINDOW TO THE SIZE OF YOUR LIKING...
xsize = 400
ysize = 400
window, XSIZE=xsize, YSIZE=ysize
plot_win = !d.window
; FIRST TRY TO DO THE FOLLOWING BY JUST PLOTTING TO THE WINDOW OVER AND OVER...
for i = 0, 100 do begin
plot, findgen(100), (findgen(100)/50.-1)^(i mod 10), XS=19, YS=19, XR=[0,100], YR=[-1,1]
endfor
; NOW LET'S OPEN A PIXMAP IN MEMORY WITH THE SAME SIZE AS THE DISPLAY WINDOW...
window, /FREE, XSIZE=xsize, YSIZE=ysize, /PIXMAP
pix_win = !d.window
for i = 0, 100 do begin
; FIRST, SET THE PIXMAP WINDOW AS THE PLOTTING DEVICE...
wset, pix_win
; NOW PLOT TO THE PIXMAP...
plot, findgen(100), (findgen(100)/50.-1)^(i mod 10), XS=19, YS=19, XR=[0,100], YR=[-1,1]
; NOW SET THE DISPLAY WINDOW TO BE THE CURRENT PLOTTING DEVICE...
wset, plot_win
; NOW DUMP THE PIXMAP INTO THE DISPLAY WINDOW...
device, copy=[0,0,!d.x_vsize,!d.y_vsize,0,0,pix_win]
endfor
Clever way to convert a range that runs from [-180,180] to [0,360]...
IDL> x = (l + 360.0) mod 360.0
To get things from [0,360] to [-180,180]...
IDL> l = x - (long(x)/180)*360.0
When making the step to IDL 6.2, this will come in handy to make the
transition smooth and you only have to do this once. The new preference system
is nice for setting preferences across IDL sessions.
; THIS IGNORES OBSOLETE VALUES THAT ARE SET FROM OLD VERSIONS OF
; IDL, LIKE !EDIT_INPUT AND WON'T COMPLAIN ABOUT THEM...
pref_set, 'IDL_preF_OBSOLETE_WARN', 'False', /COMMIT
;THIS SETS THE NUMBER OF LINES FOR THE RECALL BUFFER...
pref_set, 'IDL_RBUF_SIZE', 200, /COMMIT
; THE FOLLOWING THREE ALLOW YOU TO SET THE DEFAULT WIDTH AND HEIGHT OF
; ANY NEW WINDOW... IF YOU LIKE SMALL WINDOWS, SO YOU COULD MAKE THE
; VALUES 256...
pref_set, 'IDL_GR_X_QSCREEN', 'False', /COMMIT
pref_set, 'IDL_GR_X_WIDTH', 256, /COMMIT
pref_set, 'IDL_GR_X_HEIGHT', 256, /COMMIT
Also, if you don't want to shut off the obsolete warnings and you've got some
code, like in your IDL startup script, that is now obsolete, like:
!EDIT_INPUT=200 ; # OF LINES SAVED...
then you can prevent its use by throwing a version check in:
if (!version.release lt 6.2) then !edit_input=200
To convert thickness in X to thickness in PostScript, multiply by
0.283465. This
In IDL 6.2, you can set your preferences to always display windows
of a given size by setting the following:
IDL_GR_X_QSCREEN USERFILE = False(0)
IDL_GR_X_HEIGHT DEFAULT = 512
IDL_GR_X_WIDTH USERFILE = 512
The default is to have IDL_GR_X_QSCREEN set to true and this causes an
IDL window to fill up one quarter of the screen area. Which is just too
damn big, especially on a multi-headed display. When this is set,
obviously, the HEIGHT and WIDTH preferences are ignored.
However, if on UNIX or LINUX, the IDL installation will try to run an
X11 resources file which sets default values different than your
preferences. Since you're probably not root, you can't change these, so
the best thing to do is get everything configured the way you want it
and then shut off the damn preferences-obsolete warnings:
IDL_preF_OBSOLETE_WARN USERFILE = False(0)
!p.multi can be a pain. Say you need to make 3 plots, each one a
spectrum in addition to shared-axis plot of the residuals from a fit.
Well, the quick way to get this done well is the following:
(1) First, define the !p.multi; let's say we want to stack three of these plots.
IDL> !p.multi = [3,1,3]
(2) Next, let's establish a dummy plot to determine where IDL would like to
place a simple plot in this region. (Make sure to include the /NOERASE keyword
since once a plot command has been issued without a /NOERASE, the queue is
advanced to the next !p.multi region!):
IDL> plot, [0], CHARSIZE=3, /NOERASE, XSTYLE=4, YSTYLE=4
(3) The arrays !x.region and !y.region contain the normalized region of the
window that the plot will be constrained to. The array !y.window contains the
normalized positions of the bottom and the top axes (so, obviously, you have to
issue a plot command to establish these variables, and we did that above.)
Similarly, !x.window contains the normalized coordinates of the left and right
axes. So let's save them and then plot the spectrum assuming that we'd like the
spectrum plot to take up the top 70% of the area of the dummy plot and the
residual plot to occupy the lower 30%:
IDL> xax = !x.window
IDL> yax = !y.window
IDL> plot, [0], CHARSIZE=3, /NOERASE, POSITION=[xax[0],yax[0]+0.3*(yax[1]-yax[0]),xax[1],yax[1]], XTICKFORMAT='(A1)'
Notice that we have told IDL to reserve a single blank character for the X label
format, but we haven't provided any input for the label names, so we get no X
labels. This is a little bit of IDL kung-fu.
(4) Now we plot the residual spectrum right below the true spectrum plot. This
plot will have X labels, but we must be sure to again include the /NOERASE
keyword:
IDL> plot, [0], CHARSIZE=3, /NOERASE, POSITION=[xax[0],yax[0],xax[1],yax[0]+0.3*(yax[1]-yax[0])]
You might notice that leaving off the /NOERASE keyword wipes out the previous
plot in this region, so the only way to move to the next !p.multi region is to
explicitly queue up to it.
(5) Next, move the !p.multi queue up and repeat all the above:
IDL> !p.multi = [2,1,3]
If you need access to the shell, just type $ and enter. Type exit
to get back to IDL.
It took me about 6 years to discover how to move up a level in scope when
your subroutine has been stopped (by error or by choice). The secret is the
.OUT executive command. There isn't A WHOLE HELL OF A LOT about this in the IDL
manual, and what it does say is easy to misinterpret. So here is an explanation
of how to get around when debugging.
.continue (.c) continues execution
use .out to continue within the module, but execution is stopped when
returning to the calling routine.
.return -> continues execution of routine until a return statement is
encountered; at this point it stops and you can inspect local variables
before returning to the next-higher program level (the calling routine).
[A note: a hard return does not need to be encountered! When you've
reached the end of an IDL procedure, there is an implicit(sp?) return
call.
return ->
.skip [n] -> jump over lines that might be causing problems. can skip n lines
like so: .skip n
.step [n] -> (.s [n]) executes statements, then stops.
.stepover [n] -> (.so [n]) ???
.trace -> ?
.rnew -> convenient because it returns you to the main level and erases all
variables except those stored in common blocks
.reset_session -> wipes everything. starts anew.
I had some problems trying to get DirectColor working on Linux
displays. I ended up talking to an "IDL developer" about this. Some
solutions to getting DirectColor up and running were: make sure the
Colormap Focus, controlled by the WM, is being applied to the image
window... try clicking on the title bar of the image window to see if
the colormap is then applied; try switching WMs (I found
TWM,MWM,ICEWM,FVWM,WindowMaker all behaved properly; GNOME, KDE,
BLACKBOX, OPENBOX behaved weirdly).
The magic solution was provided by the IDL developer: there is an undocumented
keyword to device called INSTALL_COLORMAP which fixes the DirectColor problem on
all the Linux machines I've tested it on. When IDL starts up, before displaying
any images, just type (or include in your startup file):
device, /INSTALL_COLORMAP
Then you can try to make DirectColor work using this IDL batch file. You will need
this routine, called ct_fiddle.pro.
When asked, I thought that it would be super easy to put a "dot"
above a symbol, a standard way to denote a time derivative in physics.
(This is Newton's notation for a time derivative [two dots for a
second derivative] and he called derivatives "fluxions".) Anway, it
ain't. So don't waste your time trying. Just position the "dot"
using XYOUTS, which sucks since there's no way to make it independent
of device. So don't measure time derivatives.
Shutting off compilation messages. Is a bad idea. If you really
don't want a message like:
% Compiled module: STRSPLIT.
to pop up in the middle of a routine, then you could either surround
the call to strsplit() with !QUIET=1 and !QUIET=0 (which is a bad
idea... what if something bad happens in that call?!) or you could
stick this at the front of the module:
resolve_routine, 'strsplit', /IS_FUN, /NO_RECOMPILE
Then, the first time we run the routine, if STRSPLIT() is not
compiled, it will get compiled right off the bat. On subsequent
calls, it just gets ignored. And there's the tidiest and safest
solution.
So if you have special functions or procedures that are only used
by one routine (helper routines, in the IDL parlance), you can prevent
compilation messages from being output by placing this at the head of
your helper routines:
compile_opt idl2, hidden
A side-effect of making a routine hidden is that IDL will not print a
"Compiled module" message for it when it is compiled from the library
to satisfy a call to it. This makes hidden routines appear built-in to
the user. Also, the routine will not be displayed by help unless the
/FULL keyword is set. However, explicitly compiling the main routine,
will produce "Compiled module" messages for all the subroutines stored
in the .pro file.
For a second I thought that checking to see if a keyword is set
by using:
if (N_elements(x) gt 0)
might be slower than
if (keyword_set(X) OR arg_present(X))
but a few benchmarks show that this is just not true, not even for
gigantic arrays when the latter method is a little slower... which
probably makes sense since the number of elements is probably stored
as some fundamental descriptor for each array, in which case the
latter method would have to do more operations than the former.
Avoiding a floating underflow! The way to do this is to avoid
having the result of a calculation be below the minimum representable
floating point value. So, in the case of a Gaussian profile (that's
where I see it the most) you can use a mask to avoid the underflow:
arg = -0.5 * ( (x-center) / sigma )^2
minvalue = (machar(DOUBLE=(size(x,/TYPE) eq 5))).xmin)
mask = abs(arg) lt abs(alog(minvalue))
gaussian = offset + mask * height * exp(arg*mask)
Here you can see that mask sets the offending arguments to zero so
that the result of the exponential will be unity rather than a number
less than the minimum representable number! But we really want these
values to be zero, not unity, so that's why we multiply by the mask as
well.
Now, in this case, a few values may be on the very edge of underflow,
so multiplying by a value less than one might push them under. In
which case, if you were really worried about this, I suppose you would
make your mask like so:
mask = abs(arg) lt abs(alog(minvalue/height))
But the big idea is that in most case you don't care so much about
floating-point underflow. (There are cases, like in non-linear
least-squares fitting where you do care and should avoid them.) So,
yeah, learn to live with the underflow in day-to-day life.
The convention is that routines that modify the current color
table should read it from R_orig, G_orig, and B_orig, then load the
color table using TVLCT and leave the resulting color table in R_curr,
G_curr, and B_curr.
Concatenating structures: there was a time when the following
would break IDL:
IDL> a = {name:'Robishaw',awesomeness:100}
IDL> b = {name:'Simon',awesomeness:7}
IDL> c = [a,b]
% Conflicting data structures: B,concatenation.
Now it doesn't. As of IDL 5.4, IDL has relaxed its rules for
structure concatenation.
It took many years to discover this, but it's damn cool! Used to be
that everytime I wanted to create a loop at the command line, I'd use
the typical &$ notation at the end of every line. This becomes
cumbersome after a few lines, especially if you forget to end a line
with &$. Turns out, if you just type .run at the prompt, you can enter
a complete program block line by line:
IDL> .run
- for i = 0, 10 do begin
- print, i
- print, 2*i+1
- endfor
- end
This saves you the frustration of having to buffer lines with &$! Also,
these lines are no stored in the line history such that you can use
up-arrow callback to retrieve them.
On a related note, you can save some RSI by including a test call to a
routine after the last "end" statement in a routine's file, followed by another
"end" statement:
pro yo, name
print, 'yo '+name
end
yo, 'bo'
end
Now, instead of using .compile, use .run and the test at the end of the routine
gets run automatically...
IDL> .run yo
% Compiled module: YO.
% Compiled module: $MAIN$.
yo bo
Check performance of SORT() on new OS's. The underlying sort
routines IDL uses (which are built into operating system libraries) are
not always built to distingish between identical values in the list.
So, make sure the following example returns indices in increasing order
rather than randomly (or use Goddard's BSORT()):
IDL> print, byte(sort(['a','a','a','a','a','a','a','a','a']))
0 1 2 3 4 5 6 7 8
The IDL online help might not tell you about it, but both MIN()
and MAX() accept the DIMENSION keyword; so if you have a
mulitdimensional array, you can find the extrema along a given
dimension with this keyword.
IDL> print, min(bindgen(3,3,3),DIMENSION=3)
0 1 2
3 4 5
6 7 8
We've covered the weirdness of CHARSIZE below. How about
thickness (lines and characters)? Well, for characters, the deal is
simple: you can only specify integer thicknesses. Now, it's saved as
a floating point number in !p.charthick, but go ahead and try to plot
fractional charsize... it always takes the FLOOR() of the value. For
PostScript font (or any hardware font) this keyword is absolutely
useless; try it and see if you don't believe me.
Thickness is a little tricky; a thickness of 1.0 is the default. This
means 1 pixel. By nature of displaying on a monitor, you can't make
lines with a thickness of fractional pixels!!! So, you have a similar
situation to the CHARTHICK keyword... the fractional THICK value will
be FLOOR()ed; a thickness of 3.4 or 3.9 on your monitor will always be
displayed as 3.0 pixels. Now, for PostScript output, it looks an
aweful lot to my eye like this FLOOR()ing happens too, but some
reliable sources say that fractional line thicknesses appear in
CorrelDraw with different point values. So, what can I say? It's not
going to make much difference to the eye, but sure, you can use
fractional values for PostScript thicknesses.
The NLEVELS keyword to CONTOUR is useless. It's supposed to make
NLEVELS evenly-spaced levels. It doesn't always. In fact, if you
display the same data multiple times with the same exact CONTOUR call,
NLEVELS will produce spookily different results. Fanning contacted
RSI about this and here's what he found:
"I contacted RSI technical support and learned that contrary to what
the IDL documentation claims, the NLEVELS keyword does not divide the
data range into "this number of equally spaced contour
intervals". Rather, this is taken as a (I'm not kidding) "suggestion"
to the CONTOUR command, and the CONTOUR command can apparently do what
it likes with the suggestion. Including, as I understand it, put it
where the sun don't shine."
The moral is always use the LEVELS keyword and set the levels your own
damn self.
When placing an axis above a plot like so,
IDL> axis, XAXIS=1, XTIT='This is an X title.'
IDL puts the (bottom of the) title 1.5 characters above the top axis.
However, this is very, very close to the axis labels. But when the
same routine (or PLOT or CONTOUR, etc.) draws the lower X axis, it
puts the (bottom of the) title 2.8 characters below the bottom axis.
So I use xyouts to place a title above the top axis, and I place it
0.5 + 1.0 + 0.4 = 1.9 characters from the axis (assume the character
size is stored in CHSZ):
IDL> axis, XAXIS=1, CHARS=chsz
IDL> xyouts, 0.5*total(!x.window), !y.window[1] + 1.9*chsz*!d.y_ch_size/!d.y_vsize, /NORMAL, ALIGN=0.5, xtitle, CHARS=chsz
For rotating a YTITLE when placing it on the right axis... well,
there's no easy way to do this. The best you can do is determine a
reasonable x position with the cursor and then use the above logic to
center the axis:
IDL> axis, YAXIS=1, CHARS=chsz
IDL> cursor, xcen, ycen, /normal
IDL> xyouts, xcen, 0.5*total(!y.window), /NORMAL, ALIGN=0.5, ORIENTATION=-90.0, ytitle, CHARS=chsz
Getting the character size right: If you set the keywords
XCHARSIZE and/or YCHARSIZE (or, equivalently, the system variables
!x.charsize or !y.charsize) and also set the CHARSIZE keyword, then
the resulting character size on your display will be the product of
the XCHARSIZE and the CHARSIZE. CHARSIZE does not override
X/YCHARSIZE, but multiplies them! Obviously, this is something to
keep in mind!
Similarly, there is an ambiguity in setting TICKLEN along with
XTICKLEN and/or YTICKLEN. If TICKLEN is set in addition to XTICKLEN
or YTICKLEN, then the value for XTICKLEN or YTICKLEN overrides teh
value of TICKLEN.
IDL> plot, [0], xticklen=0.2, ticklen=0.4
I'm always forgetting how the plotting styles work, so here is an
extended table of what to set the x(yz)style keyword to. Set multiple
effects by adding together basic options (keyword is set bitwise):
Value | Result |
1 | Force exact range. |
2 | Extend axis range. |
4 | Suppress entire axis. |
8 | Suppress box style axis (draw only one side) |
16 | Inhibit setting Y axis minimum to ZERO. |
| |
More | Other good stuff. |
3 | Force exact axis range and add a little buffer. |
5 | Suppress entire axis yet force exact range. |
7 | Suppress entire axis, force exact range with little buffer. |
Tidbits I picked up from Carl while at Green Bank: PSYM=-4 plots
diamond symbols in addition to connecting the data points with a line;
to use help on more than one variable, help, name='val*'.
It is really important to consider the order of operations in an
expression. For instance, in the expression,
B = A * 16. / MAX(A)
if A is a large array of N elements, then you first multiply each
element of A by 16 (N operations) and then divide each element by the
max of A (N more operations) for a total of N^2 operations (not
counting the operations that go into getting the max of A!)
However! If instead you write,
B = 16./MAX(A) * A
then you divide 16 by the max of A (1 operation) and then multiply
each element in A by this value (N operations) for a total of N+1
operations. This is clearly significant for large arrays!
Access large arrays by "memory order." IDL indices are
column-major, that is, the column is the first index: ARR[COL,ROW].
The memory order increases with column before row!
ARR[0,0] = ARR[0]; ARR[1,0] = ARR[1]; ARR[2,0] = ARR[2]; ...
So, the big idea is to loop over the indices in increasing index
order. Loop over the columns first, then the rows:
IDL> FOR Y = 0, 511 DO FOR X = 0, 511 DO ARR[X,Y] = ....
This is faster than:
IDL> FOR X = 0, 511 DO FOR Y = 0, 511 DO ARR[X,Y] = ...
(RSI says it's 50x faster. I don't find this to be true; but it's
still faster!) The latter is slower because the Y loop is grabbing
successive rows in a specific column, BUT these data are not
successive in memory! So with each step, you're jumping all over your
matrix to access the next element. But the first method is much
quicker since each successive element of the array is accessed in the
order that it's stored in memory. Nice and efficient.
In my business, I'm dealing with spectral line data cubes. These
cubes have 3 dimensions: X and Y positions and velocity (or
wavelength). The question is how to arrange the cube: should it be
arranged in the order [X,Y,V] or [V,Y,X]? The answer is: it depends!
We just saw that it's most efficient to access an array in memory
order. So if we were interested in accessing the X-Y image at
multiple velocities, we'd want to arrange the cube in the order
[X,Y,V]... then you'd access the images in the most efficient manner:
IDL> for v = 0, 127 do operate_on_image(cube[*,*,v])
If you were interested in accessing the spectra at various positions,
then you'd want the cube to be arranged in the order [V,X,Y]:
IDL> for x = 0, 127 do for y = 0, 127 do operate_on_spectrum(cube[*,x,y])
Ah, but that's not right! We just saw above that it's much faster to
access the array in memory order, so we really want the inside loop to be
over X, not Y:
IDL> for y = 0, 127 do for x = 0, 127 do operate_on_spectrum(cube[*,x,y])
Transposing a gigantic cube (a typical cube I'm working with has a few
hundred million datapoints... [512,512,2048]) can take a wicked
long time, but a few benchmarking tests show that it's actually faster
to transpose a cube and access the spectra in memory order than to
simply access the spectra in non-memory order!! As an example, if we
have a cube that's of size [512,512,2048], then to acces all the
spectra, we should first transpose it and access the spectra in memory
order:
IDL> cube = transpose(cube,[2,0,1])
IDL> for y=0,511 do for x=0,511 do spectrum = cube[*,x,y]
This is faster than:
IDL> for y=0,511 do for x=0,511 do spectrum = cube[x,y,*]
The following result shocked me to the core. It is disturbing
and worth committing to memory.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
****MEMORY WITH SUBSCRIPTS HERE*****
NEVER, EVER, EVER USE AN ASTERISK ON THE LEFT HAND SIDE OF AN
ASSIGNMENT STATEMENT (IF IT CAN BE AVOIDED)!!! Here's an example:
IDL> FOR Y = 0, 10 DO ARRAY[*,Y] = SHIFT( ARRAY[*,Y], Y)
IDL first creates a temporary variable, places the contents of the
right hand side into this temporary variable, and then places the
temporary variable back into ARRAY. This is bad news for huge arrays.
You have to use a little trick (this is pretty sneaky):
IDL> FOR Y = 0, 10 DO ARRAY[0,Y] = SHIFT( ARRAY[*,Y], Y )
This just begins to assign the result of the right hand side in memory
order starting at location ARRAY[0,Y]. This will increase speeds up
to a factor of 10 to 20. Be careful with sizes:
IDL> A = FLTARR(100,100)
IDL> A[0,3] = FINDGEN(10)
Starting at A[0,3], we store only ten values in memory order. You can
do this for 100 values as well. But if you try 101 values:
IDL> A[0,3] = FINDGEN(101)
% Out of range subscript encountered: A.
Now you're attempting to put 101 values into a row that only has 100
elements. So this sneaky trick increases speed but leaves room for
errors... you should give some thought to its implementation in your
code so that you don't end up underfilling a row (which won't crash
your program!) or overfilling a row (which will crash your program!)
There is a big restriction here: this can ONLY work for the first
dimension. You HAVE TO use an asterisk if filling dimensions two or
greater. (Of course, you could transpose the matrix, but you might be
sacrificing clarity for speed, which shouldn't be done unless speed
really is an issue.)
However, you can be clever and place a 2D matrix into a larger 2D
matrix:
IDL> A = INDGEN(4,4) & A[1,1] = INTARR(2,2)
IDL> PRINT, A
0 1 2 3
4 0 0 7
8 0 0 11
12 13 14 15
Wait, you can do this for any array, no matter what the dimensions are!
IDL> A = BYTARR(4,4,4) & A[1,1,2] = (BYTARR(2,3,2)+1)
IDL> print, a
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
0 1 1 0
0 1 1 0
0 1 1 0
0 0 0 0
0 1 1 0
0 1 1 0
0 1 1 0
Be very careful when comparing two arrays: make sure that they
actually have the same number of elements! IDL has a feature
that causes the result of an expression involving arrays of different
sizes to be trimmed to the smaller of the two arrays:
IDL> print, [1,2,3,4] ge [0,3]
1 0
This might be useful for some weird purpose. But it's more likely to
just trip you up.
Use TEMPORARY to conserve memory if you're using wicked huge
arrays. But don't get crazy with it. It used to advantage when a
variable containing an array on the left hand side of an assignment
statement is also referenced on the right hand side:
IDL> A = TEMPORARY(A) + 1
There is no sense in using TEMPORARY unless you have at least two
variables or constants on the right hand side of the equal
sign. Neither of the following saves any memory:
IDL> A = FLOAT(TEMPORARY(A))
IDL> A = TRANSPOSE(TEMPORARY(A))
Use /OVERWRITE keyword when using REFORM function.
Use the MATRIX_MULTIPLY function if either or both of the arrays
are transposed! It's faster. It just is. Otherwise, they are
EXACTLY equivalent. Use ATRANSPOSE and BTRANSPOSE keywords to save
memory when using big arrays that need transposing.
**** REVIEW CHECKING MEMORY USAGE HERE...
If you have some ridiculously huge array that is no longer needed, you
should use temporary to free up the memory, or even just set it equal to
0B.
IDL> print, MEMORY(/CURRENT)
856672
IDL> a = findgen(10000,10000)
IDL> print, MEMORY(/CURRENT)
400856828
IDL> a = 0b
IDL> print, MEMORY(/CURRENT)
856685
To determine how much dynamic memory is required to execute a sequence
of IDL code:
; Get current allocation and reset the high water mark:
start_mem = MEMORY(/CURRENT)
; Arbitrary code goes here.
PRINT, 'Memory required: ', MEMORY(/HIGHWATER) - start_mem
IDL> a = lonarr(2048,2048) & tmp=memory() & a = transpose(a)
IDL> print,memory(/high)
33991830
IDL> a = lonarr(2048,2048) & tmp=memory() & a = transpose(temporary(a))
IDL> print,memory(/high)
33991845
Keyword_set should only be used with binary flags! If you use it for
keywords that can have values other than 1 or 0, you are using it
incorrectly. Sooner or later you're going to want to pass in a value
of ZERO as a keyword and your code will fail because rather than
accepting that the keyword can have this value, your code will be
tricked into thinking the keyword was not set:
IF KEYWORD_SET(NOT_A_BINARY_FLAG) THEN VALUE = NOT_A_BINARY_FLAG + 5
See. If NOT_A_BINARY_FLAG is set to zero, VALUE will not be
calculated.
Be very careful when using NOT with binary flag variables. I've
seen some very experienced people make this silly mistake:
IDL> NOTSET = NOT KEYWORD_SET(THISKEYWORD)
If you expect the value of NOTSET to be 1 if THISKEYWORD is not set,
or 0 if it is, you will be very surprised.
Instead, you need to use integer comparison operators:
IDL> NOTSET = KEYWORD_SET(THISKEYWORD) EQ 0
OK. Fine. The weirdness here is that AND, OR, NOT and XOR are
bitwise operators and behave oddly on integers (unless you
understand bitwise operations: then it behaves perfectly reaonably
[Look at help for !D.FLAGS if you want a lesson]) but behave as
expected for floating point values:
IDL> print, not (-3), not (-3.0)
2 0.00000
But keyword_set returns an integer, so you're stuck with using a
comparison operator in that case.
It's important to be aware of keyword inheritance. Keywords can
be passed by value or by reference. To pass a keyword
by value is another way of saying that in the calling program you are
only sending a value in your routine call, you are not sending the
location in memory that is assigned to a variable. Here is an
example:
result = thisfunction(data, thiskeyword=array[1:4], thatkeyword=5.0, /aflag)
All three keywords above were passed by value: thiskeyword is only
getting a few values from the variable array; thatkeyword is only
getting the value 5.0; /aflag is being set to unity. None of the
keyword parameters is being sent a variable, so nothing can be
returned from the routine through these keywords.
To pass by reference, you pass the name of a variable to a routine.
This variable name is just a pointer to a memory location. So now you
can change the memory location inside the routine and the value stored
in the variable will be changed when you return to the calling
routine:
result = thisfunction(data, referencekeyword=array)
If array is changed inside THISFUNCTION, it will come back to the calling routine
So, here's the really useful stuff: the _EXTRA and _REF_EXTRA keywords!
If you define a routine with the keyword _EXTRA, as so,
pro thisroutine, data, XOFFSET=xoffset, YOFFSET=yoffset, _EXTRA=_extra
then pairs of unrecognized keywords and values are placed in an
anonymous structure. These are passed by value! If you want to pass
keywords by reference, you need to use _REF_EXTRA. You can't use
both! You should most always use _REF_EXTRA (since no structure needs
to be created, there's less overhead.) BUT, if the routine that is
passing the values through needs access to these keywords, then you
have to use _EXTRA. So in this case, if you can, explicitly define
these keywords. Here is an example:
pro thisroutine, data, XOFFSET=xoffset, YOFFSET=yoffset, _REF_EXTRA=_extra
xoffset=5
yoffset=5
device, XOFFSET=xoffset, YOFFSET=yoffset, _EXTRA=_extra
end
You wouldn't have been able to access xoffset and yoffset if they were
passed in through _REF_EXTRA. Here's a trick:
pro thisroutine, data, _REF_EXTRA=_EXTRA
xoffset=5
yoffset=5
device, XOFFSET=xoffset, YOFFSET=yoffset, _EXTRA=_extra
end
In this case, if XOFFSET and YOFFSET were sent in via _REF_EXTRA,
these are the values that are sent into DEVICE! Even though you
explicitly pass the XOFFSET and YOFFSET keywords by reference in your
call to DEVICE, if you pass these keywords by reference through
THISROUTINE, they will hold precedence in the call to DEVICE. An
example for clarity:
IDL> THISROUTINE, DATA, FILENAME='foo.ps'
The keywords XOFFSET and YOFFSET are sent to DEVICE with the value 5.
IDL> THISROUTINE, DATA, FILENAME='foo.ps', XOFFSET=1, YOFFSET=1
Although it looks like XOFFSET and YOFFSET should be sent to DEVICE
with the value 5, they are sent the value 1. This can be very useful.
I've always wondered whether I was getting away with anything
when I ignore "Floating Point Underflow" errors.
To make a Gaussian distribution with a given mean and sigma:
IDL> noise = randomn(seed,1024,/DOUBLE)
IDL> noise = (noise-mean(noise))/stddev(noise) * sigma + mean
I have to figure this out every time, so here goes. When
scaling things into a BYTE array from 0 to 255, you have to realize
that there are really 256 INTERVALS. So a datum gets stuck in the ith
bin if:
BIN[i] = i*255./256. <= datum < (i+1)*255./256.
This EXCLUDES the value 255, so 255 should just get stuck in the last bin.
N.B., most everybody does this wrong including Carl and Fanning.
BYTSCL does it right. You can test it this way:
IDL> a = findgen(1000)/999.
IDL> h = histogram(a*255,bin=255./256.,min=0,max=255,reverse_ind=r)
IDL> h = where(h gt 0,nh)
IDL> b = byte(a)
IDL> for i = 0, nh-1 do b[R[R[h[i]]:R[h[i]+1]-1]] = h[i]<255
The array B will be equivalent to what would pop out of BYTSCL.
Many image programs will do an interpolation to assign color table
indices. Suppose you have 15 colors available. Then your color table
only has 15 indices. Most image programs say something like this
(loadct.pro does this!):
IDL> nc = 15
IDL> print, byte((lindgen(nc) * 255) / (nc-1))
0 18 36 54 72 91 109 127 145 163 182 200 218 236 255
BUT:
IDL> print, bytscl(bindgen(nc))
0 18 36 54 73 91 109 127 146 164 182 201 219 237 255
There's quite a difference there. WHY is there a difference? The
BYTSCL method first scales the input between 0 and 255 and then bins
the resulting array into bins of width 255/256. But the interpolation
method scales everything between 0 and 255 but then bins the result
into bins of width 1.0:
IDL> nc = 15
IDL> a = 255*findgen(nc)/(nc-1)
IDL> print, byte(a)
0 18 36 54 72 91 109 127 145 163 182 200 218 236 255
IDL> h = histogram(a,bin=1.0,min=0,max=255,reverse_ind=r)
IDL> print, byte(where(h gt 0,nh))
0 18 36 54 72 91 109 127 145 163 182 200 218 236 255
This means you've only allowed for 255 equally spaced bins between 0
and 255 and you've allowed an entire bin to be taken up by the
singular value 255. An awful waste of space. So do it right: use
BYTSCL.
This is the most thought I ever want to give to this.
So... BYTSCL works.
Here are a few useful ideas to help with the debuggery:
To compile a procedure or function from within a
procedure, employ the RESOLVE_ROUTINE procedure. It takes the module
name of the program you want to compile:
resolve_routine, 'setcolors'
[N.B. While in Green Bank, Carl discovered that this won't work if the
file name containing the routine has capital letters and the
routine has not been compiled. If the IDL newsgroup is still
around then here is my post.]
If the module is a function, you need to set the keyword /Is_Function.
Or, if you want to make things general, use the /EITHER keyowrd.
N.B. If you have more than one module in a file, you should keep the
module with the name of the file at the very end of the file. The
rules for compiling automatically cause compiling to stop once the
module with the program's name is compiled. I used to think was lame,
but now it just makes life easier to conform.
If you have a program module (say, pro getmean) within a .pro file
(say, spectrum.pro) which has the same name as a .pro file
(getmean.pro) in your IDL path, compiling of the first .pro file
(spectrum.pro) will give its program module (pro getmean) precedence
over the actual .pro file (getmean.pro).
If you're concerned about which one is currently enabled (and this
trick is just downright useful when debugging) use the the
ROUTINE_INFO function:
IDL> f = routine_info('getmean',/source)
IDL> print, [transpose(f.name),transpose(f.path)]
If you don't supply a module name, you will receive info for all
compiled routines. However, any routines that are unresolved
will have a null string for the path field of the returned structure.
The RESOLVE_ALL procedure iteratively resolves any uncompiled
user-written or library functions called by all currently-compiled
routines. This is very helpful for the following tasks: (1) Finding
which procedures or functions your new routine depends upon
(especially if you've written something way too huge); (2) Finding out
if your routine depends on another routine which is not in your IDL
search path... you'd rather find this out before you run some
long program and have it crash while you're at home sleeping.
So, there's probably a clever way to use resolve_routine iteratively
to determine all the dependencies of a routine. Someday I'll work on
this.
You can set a trap for invalid mathematical computations
(exceptions), such as division by zero. To immediately report
exceptions at the end of each IDL statement, set !EXCEPT=2. Note that
this slows IDL by roughly 5% compared to setting !EXCEPT=1, ehich is
the default (report exceptions when the interpreter is returning to an
interactive prompt.) You can use CHECK_MATH to prevent such an error
from occuring if it's not possible to completely avoid such an error.
To fill a plot background with a color, you can set the BACKGROUND
keyword to the color index of your choice. For instance, using gray for
a background allows you to see colors like blue, yellow, red and green
together (try seeing dark blue against a black background or green (or
yellow) against a white background!)
IDL> plot, findgen(30), color=!blue, background=!gray
Alternatively, you could open a window, then erase the contents giving
the ERASE procedure the color index you wish to fill the window with as
an argument:
IDL> window
IDL> erase, !gray
IDL> plot, findgen(30), color=!blue, /NOERASE
The warning here is that you have to provide the /NOERASE keyword for
any future plotting in this window or the background will be erased!
This form of background filling is not supported on all devices, notably
PostScript!
To get a black background when making a PostScript plot:
IDL> POLYFILL, [1,1,0,0,1], [1,0,0,1,1], /NORMAL, COLOR=0
IDL> plot, data, /NOERASE
Don't forget the /NOERASE when you plot over the polyfill! A more
general way is to use my BGFILL.PRO routine. You can fill the
background with any color index you want in this fashion.
To suppress a carriage return (say, for asking questions) use:
IDL> print, format = '($,"Ask a question: ")'
or even better, just use:
IDL> read, io, prompt='Ask a question: '
Can't find that IDL symbol? Check out Steve's DIY IDL Font page.
Sub-solar symbol when plotting = !D!9n!3!N
Some useful ASCII codes:
Bell = string(7B); Backspace = string(8B);
Formfeed = string(12B); Linefeed = string(10B);
Carriage Return = string(13B); Horizontal Tab = string(9B);
Vertical Tab = string(11B)
Calling back a command from a previous line = <SHIFT><6>
To change a variable to match the type of another variable, use
the FIX function with the TYPE keyword set to the type code of the
variable to match:
IDL> binky = fix(binky,TYPE=size(bongo,/type))
HERE
is a list of things you should do to make IDL run more efficiently,
straight from tech support at RSI.
Adding axis annotations on top and/or to right of plot. In the
plot command, set both the xsytyle and ystyle keywords to 8
(suppressing the top and right axes) and reserve a little extra room
for the annotations by using the xmargin and ymargin keywords:
xmargin=[8,8], ymargin=[4,4] ([left/bottom, right/top]). Then use the
AXIS procedure with xaxis=1 set for top x-axis. A call to AXIS with
yaxis=1 draws the right y-axis.
If you want to have a logarithmic axis opposite to a linear axis,
well, that's more tricky. Go read about it on Fanning's site.
You can keep a journal of command line arguments by using the
JOURNAL procedure.
IDL> journal, 'stuff.pro'
The journal is kept until IDL is exited or journal is called with no
argument.
If you want to get notice of invalid numerical computations as
soon as they occur in a program (the default is to wait until the
interpreter returns to an interactive prompt), you need to set the
!EXCEPT system variable to 2. This slows IDL by 5%! To return
to the default, set !EXCEPT to 1. Set it to 0 for absolutely no
warning.
A word about changing an image's size with congrid. (Like
me, Carl Heiles admits to having to rediscover how this works each
time he uses it.) You could use rebin to make life easier, but
that only works if you change to a size that is an integral multiple
or factor of the original. That's pretty unlikely, so you'll have to
use the more versatile but slower congrid. So if you're going
to use congrid... finish this thought.
Here's the way to determine which mouse button was clicked when
you use the cursor command: examine the !Mouse.Button field.
Here are the values of !Mouse.Button:
0 : No button has been clicked yet.
1 : The left button was clicked with the cursor command.
2 : The middle button was clicked.
4 : The right button was clicked.
Once you've clicked a mouse button, that value remains in
!Mouse.Button, so if you are looping until a mouse button is clicked
(i.e. while (!mouse.button eq 0) do) then you'd better be careful to
set the !Mouse.Button field back to zero after clicking on the mouse!
The RETAIN attribute determines how the backing store is
performed in the draw area. When part of a window that was covered is
exposed, the window system can repair the damage since it kept track
of the contents of all windows (retained windows... the saved
information is called the backing store) OR the window system can
report the damage to the program that created the covered window and
let the program repair the damage (non-retained windows.) Here are the
values RETAIN can have (it can be called as a keyword to DEVICE, in
which case the value is applied to all windows, or to WINDOW, in which
case it is applied only to the window created):
0: No backing store. Can increase performance since nothing is
being saved.
1: IDL requests that the window system keep the backing store.
The request may be rejected by the window server.
2: IDL keeps the backing store itself and repairs any window damage
whenever the window system requests it. A pixmap the same size
as the window is created and all graphics operations sent to the
window are also sent to the pixmap. When asked to repair damage
to a window, the pixmap is used to fill in the missing contents.
This can be a weight on IDL's shoulders.
The default is RETAIN=1. However, when reading data from windows with
the window server performing the backing store, unexpected results can
occur: use RETAIN=2.
If you have a 24-bit display you can simultaneously display all
224 available colors. However, if you have a wimpy 8-bit
display, you can only display 28 of the >16 million
available colors simultaneously. (BUT, you're probably using a few
dozen colors on your desktop and therefore aren't even using the full
256 colors available to IDL!) If you want to know what the deal with
your color table is, check this out.
If you want to make a JPEG image from the contents in one of your
windows,make sure you've sent RETAIN=2 as an argument to window (if
this is not the default) then :
IDL> device, decomposed=1
IDL> image24 = tvrd(true=1)
IDL> write_jpeg, <filename>, image24, quality=100, true=1
The above assumes you've made a 24 bit image. For 8-bit it's harder,
but talk to me if you need to know.
To get the size of your screen in pixels, use:
IDL> device, get_screen_size=screensize
Now screensize[0] is # of x pixels and screensize[1] is # of y pixels.
Using FLEXlm and a routine that will allocate new memory rapidly,
IDL will fall asleep if running on Solaris. The fix is to use the
GENVER license manager by running IDL with the genver flag:
> idl -genver
Using FONTS in IDL is a big mess. Period. IDL can use three
different FONT systems which can be set via the !P.FONT system
variable (or the FONT keyword to any plotting routines) as follows:
!P.FONT= -1L : Vector-drawn fonts, also known as Hershey
fonts. These are the default fonts of IDL... and for good reason:
they are (nearly) device-independent! Even better, they are the only
fonts that can be scaled and rotated in 3D space! They are less high
quality than TrueType or PostScript fonts, but their generality makes
them most useful.
!P.FONT= 1L : TrueType fonts, also known as outline fonts.
These take a little more computation since an outline of each letter
is drawn and then filled in with polygons! They are not completely
device-independent. What's worse is that only a few of the familiar
embedded formatting commands for vector fonts are available for
TrueType fonts. For instance, !7 will not change the font to Complex
Greek! Instead, you have to use !9.
Here's how to determine which TrueType fonts IDL recognizes:
IDL> device, GET_FONTNAMES=names, /TT_FONT, SET_FONT='*'
IDL> print, transpose(names)
Helvetica
Helvetica Bold
Helvetica Italic
Helvetica Bold Italic
Courier
Courier Bold
Courier Italic
Courier Bold Italic
Times
Times Bold
Times Italic
Times Bold Italic
Symbol
Monospace Symbol
You can establish the default font for your device (X Windows
OR PostScript) by using the SET_FONT keyword to DEVICE, however
this has no effect on vector fonts. Here is what IDL Online Help has
to say about the SET_FONT keyword:
Set this keyword to a scalar string specifying the name of the
font used when a hardware or TrueType font is selected. Note that
hardware fonts cannot be rotated, scaled, or projected, and that the
"!" commands for formatting may not work. When generating
three-dimensional plots, it is best to use the vector-drawn or
TrueType characters. Note that for the PS device, only one hardware
font (other than the predefined fonts set via the fontname keywords,
such as /AVANTEGARDE) may be loaded at a time.
If you want to select a TrueType font, you have to send the /TT_FONT
keyword to DEVICE. Otherwise it is assumed you want the PostScript
font. Below are a few examples (PSOPEN and PSCLOSE are routines that
establish a PostScript file and then close it after you've plotted
something. They take any DEVICE keywords that are applicable to the
PostScript device and can be found in ~robishaw/idl/ps).
First, make a PS file with "Monospace Symbol" TrueType font:
IDL> psopen, 'foo.ps', SET_FONT='Monospace Symbol', /TT_FONT
IDL> plot, findgen(3), FONT=1, $
IDL> xtit='Galactic Radius [kpc]', ytit='Density [cm!E-3!N]'
IDL> psclose
The FONT=1 keyword specifies that you want the TrueType font. Try
using FONT=-1... you just get the default vector font.
!P.FONT= 0L : Device fonts, also known as hardware fonts.
Naturally, these are device-dependent. The useful case for us are the
PostScript fonts. Same problem with the embedded format commands as
TrueType fonts. PostScript fonts definitely look nicer than Hershey
fonts, but you will encounter problems aligning text if you choose to
use them.
To find which PostScript fonts IDL recognizes, type SHOW_PS_FONTS. This
will produce a file called idl.ps in your home directory. Don't print
this PostScript file! It's HUGE! Use ghostview or ghostscript to
look at this and choose a PostScript font that you like.
Now, make a PS file with oblique, bold, Helvetica PostScript font with
ISOLatin1 font encoding:
IDL> psopen, 'foo.ps', /HELVETICA, /BOLD, /OBLIQU, /ISOLATIN1
IDL> plot, findgen(3), FONT=0, $
IDL> xtit='Galactic Radius [kpc]', ytit='Density [cm!E-3!N]'
IDL> psclose
The FONT=0 keyword specifies that you want the device (PostScript)
font. Try using FONT=1. You see that DEVICE has remembered that
"Monospace Symbol" was the last TrueType font you established. It
will remain the current TrueType font until you change it. Likewise
with the PostScript fonts... the oblique, bold, Helvetica PostScript
font with ISOLatin1 font encoding will remain the current PostScript
font until you change it.