Generating an Ellipsoid using IDL 8 Graphics

Author: David Starbuck

This blog post describes how you can use IDL to generate an ellipsoid such as the one shown below:

The MESH_OBJ, POLYGON, and PLOT3D routines were used to create this graphic, and the code that was used is shown at the bottom of the post (dj_ellipsoid_ng).

When using MESH_OBJ routine to create spherical object, you need to provide a two dimensional grid of radius values.The rows of this grid correspond to the longitude which goes from 0 to 360 degrees (theta). The columns of this grid correspond to the latitude which goes from -90 to 90 degrees (phi).

Since, MESH_OBJ requires the grid of radius values, and the value of the radius depends on the latitude and longitude, you must first generate latitude and longitude grids.  To generate the longitude grid (theta), I first generated a 51 element array going from 0 to 360 and converted to radians (to be used with the COS and SIN routines):

theta_1d = (findgen(51)*360/50)*!dtor

Then, I used a combination of the CONGRID and TRANSPOSE routines to replicate the values from theta_1d into a 51 by 51 element array.  The first TRANSPOSE call “theta_1d” makes the CONGRID routine accept it as a 2D array. 

theta = congrid(transpose(theta_1d),51,51)
theta = reform(transpose(theta))

I created the latitude grid (phi), using the same method: 

phi_1d = ((findgen(51)-25)*180/50)*!DTOR
phi = congrid(TRANSPOSE(phi_1d),51,51)

The next step is to determine the radius value for each longitude and latitude function.  To calculate the radius, I used the equation for an ellipsoid shown below:                


I converted this equation to spherical coordinates, and then solved it for “r”:

Using this result, I wrote the function “dj_get_rad”, which accepts the latitude and longitude grids and returns a grid of the radius values.  I then used MESH_OBJ with the radius values returned from this function to get the vertices and polygons of the ellipsoid.  

The final step is to create a visual of the data. To do this, I called PLOT3D with the NODATA keyword to generate the empty 3D dataspace. Then, I called POLYGON with the vertices argument set the vertices returned by MESH_OBJ and the CONNECTIVITY keyword set to the polygons returned it.

The "dj_ellispoid_ng" routine shows how this can be done. It requires 3 arguments which correspond to the "a", "b" and "c" parameters in the ellipsoid equation.Some sample images generated with different parameter values are shown below:

IDL> dj_ellipsoid_ng, 2,1.5,1 ;a=2,b=1.5,c=1

IDL> dj_ellipsoid_ng, 2,2,1

IDL> dj_ellipsoid_ng, 1,1,2

function dj_get_rad, theta, phi, a,b,c
  compile_opt idl2
    ;use the formula for the ellipsoid to
    ;determine the radius for various values
    ;of theta and phi.
    radius = sqrt(((a*b*c)^2)/( $
    b^2*c^2*(cos(theta)^2)*(cos(phi)^2)+ $
    a^2*c^2*(sin(theta)^2)*(cos(phi)^2)+ $

    return, radius

pro dj_ellipsoid_ng, a, b, c, data=data
  compile_opt idl2
  ;create a grid of theta values
  ;these are the longitude of the of the
  ;ellipsoid and go from 0 to 360 degrees
  theta_1d = (findgen(51)*360/50)*!dtor
  theta = congrid(transpose(theta_1d),51,51)
  theta = reform(transpose(theta))
  ;create a grid of phi values. these
  ;are the latitude of the ellipsoid and go
  ;from -90 to 90 degrees
  phi_1d = ((findgen(51)-25)*180/50)*!dtor
  phi = congrid(transpose(phi_1d),51,51)
  ;use "get_rad" function to determine the
  ;radius values at each point of the theta
  ;and phi grid
  radius = dj_get_rad(theta,phi,a,b,c)
  ;radius = get_rad(theta,phi,a,b,c)
  ;create a mesh using the radius values
  ;this ouputs the vertices (vert) and polygons
  ;that will be used to generate the plot
  ;output the data
  if (arg_present(data)) then begin

  ;determin the biggest value and create a scale from it
  m = max([a,b,c])
  scale = (findgen(10)-5)*m/5

  ;plot the scale and axis with no data. set clip=0, to
  ;prevent edges of ellipsoid from getting cut off.
  p_scale =plot3d(scale,scale,scale,/nodata,clip=0,$
                  aspect_z=1, aspect_ratio=1)
  ;use the polygon to plot the mesh. use the
  ;vertices and polygons output from the mesh_obj
  ;to fill out the data argument and connectivity keyword.
  ;use bright green as the color.
  p = polygon(vert,connectivity=pol,/data,$

;main level program
;for some reason, if 'a; does
;not equal 'b', the ellipsiod
;comes out messed up

Comments (1) Number of views (177) Article rating: 4.5




Using the Clipboard, Static Methods and ZLIB compression in IDL 8.3

Author: Jim Pendleton

When the Exelis Professional Services Group is contracted to author "large" applications for clients, we often have the luxury of building full installers that we use to place a variety of resource files and directories onto disk, along with the main functionality of an application or toolset.

In this environment, the ROUTINE_FILEPATH  function is a huge help for bootstrapping the application so it can find its resource files relative to its installation directory.

For smaller projects, however, it is often more convenient if all the resources, such as bitmap button images, can be bundled into a single file, in particular a compiled IDL SAVE file. This reduces the complexity of installation and testing.

Various routines exist for turning, say, bitmap images into IDL source code. I will show a version below that employs the new IDL 8.3 function for lossless, in-memory ZLIB compression of the image data. It also uses the new Clipboard static class so you can paste the generated code into a text editor with a single button click.

Finally, we'll investigate a utility for automatically generating an entire class of static methods which store a directory of images, greatly simplifying their accessibility.

The routine listed below takes as input an image in the form of a BYTARR along with a string that will be applied as the name of a new IDL function. It generates PRO code and places it into the system's clipboard as ASCII text. You can then paste the generated code directly into an editor window, which you will compile and execute.

PRO ImageToCode, image, functionName
 d = SIZE(image, /DIMENSIONS)
 z = ZLIB_COMPRESS(image)
 b64 = IDL_BASE64(z)
 l = LIST()
 l.Add, 'FUNCTION ' + functionName
 dims = STRJOIN(STRTRIM(D, 2), ',')
 l.Add, 'lines = LIST()'
 FOR i = 0L, STRLEN(b64) - 1, 66 DO $
    l.Add, "lines.ADD, '" + STRMID(b64, I, 66) + "'"
 l.Add, 's = IDL_BASE64(STRJOIN(lines.ToArray(/NO_COPY), ""))'
 l.Add, 'd = ZLIB_UNCOMPRESS(s)'
 l.Add, 'RETURN, REFORM(d, ' + dims + ')'
 l.Add, 'END'
 Clipboard.Set, l.ToArray(/NO_COPY)

In order to make the output code for the image data we first call the ZLIB_COMPRESS function to create an in-memory version of the image bitmap, using lossless compression. To simplify the code we will make an initial assumption that the input image is a 24-bit (RGB) image in a BYTARR.

Because we want to create IDL source code from this, we convert the binary data produced by ZLIB_COMPRESS into an ASCII format with the IDL_BASE64 function. This is a useful trick for encoding any type of binary data that needs to be exchanged in an ASCII format, for example via standard JSON objects passed to and from an ENVI Services Engine instance.

In order to make the output PRO code more legible, the output string from IDL_BASE64 is broken up over multiple lines, each with a maximum length of 80 characters. 

The new function code we create performs the inverse operations, converting the string back to binary via IDL_BASE64, then regenerating the BYTARR binary data via the ZLIB_UNCOMPRESS function.  Notice that the REFORM function call is required because the ZLIB compression doesn't encode the dimensionality of the original input data.

Try this:

IDL> f = filepath('colorbar24.png', subdir=['resource','bitmaps'])
IDL> imagetocode, read_image(f), 'cbimage'

Simply open an editor window and paste the contents of the system clipboard to it.  The imagetocode routine has populated the clipboard with the text of the routine. 

The output should look like this:

FUNCTION cbimage
lines = LIST()
lines.ADD, 'eJz6//8/w/9RPGLwuct3/udUTf2PLDZ32Y7/IIwsBlIDUossZuNX9H8gzQfpAamhFK'
lines.ADD, 'O7D4Yb+lf9r+lZ9b+ic9X/kraV/wtaVv7PaVz5P6Nuxf/UmhX/EytX/I8tW/4/snj5'
lines.ADD, '/9DC5f8D85b/981e9t8zc9l/17Rl/x2Tl/63TVz63zJu6aj5o+aPmj9qPknmj2L6Yw'
lines.ADD, 'AAAAD//wMA32owCw=='
s = IDL_BASE64(STRJOIN(lines.ToArray(/NO_COPY), ""))
RETURN, REFORM(d, 4,24,24)

Of course the translation routine could have been designed to write its output to a file directly, but where would the fun be in that?

Save and compile the new file, cbimage.pro.

Display the output by executing the function in the context of the IMAGE function.

IDL> image(cbimage())

Now for some additional fun, the algorithm can be adapted easily to create an entire class of static methods.

For example, I may want to extend this functionality to create a single static class that contains all the bitmaps in the IDL distribution's resource\bitmaps directory. Using this mechanism, I would no longer need to include the files in that directory in my application distribution. I would simply include my object class, whether in source code or in the form of compiled code in a SAVE file.  I can simply reference each bitmap via the static class method name.

PRO ImagesToStaticClass, directory, className
files = FILE_SEARCH(FILEPATH('*', ROOT = directory))
l = LIST()
FOREACH file, files DO BEGIN
  CATCH, errorNumber
  IF (errorNumber NE 0) THEN BEGIN
  image = READ_IMAGE(file, r, g, b)

  s = SIZE(image, /STRUCTURE)
    newImage = BYTARR(3, s.Dimensions[0], s.Dimensions[1])
    newImage[0, *, *] = r[image]
    newImage[1, *, *] = g[image]
    newimage[2, *, *] = b[image]
    image = TEMPORARY(newimage)

  image = TRANSPOSE(TEMPORARY(image), [1, 2, 0])
  d = SIZE(image, /DIMENSIONS)
  baseName = FILE_BASENAME(file)
  methodName = IDL_VALIDNAME(STRMID(baseName, 0, $
    STRPOS(baseName, '.', /REVERSE_SEARCH)))
  IF (methodName eq '') THEN CONTINUE
  z = IDL_BASE64(ZLIB_COMPRESS(image))
  l.Add, 'FUNCTION ' + className + '::' + methodName
  dims = STRJOIN(STRTRIM(D, 2), ',')
  l.Add, 'lines = LIST()'
  FOR i = 0L, STRLEN(z) - 1, 66 DO $
    l.Add, "lines.ADD, '" + STRMID(Z, I, 66) + "'"
  l.Add, 's = IDL_BASE64(STRJOIN(lines.ToArray(/NO_COPY), ""))'
  l.Add, 'd = ZLIB_UNCOMPRESS(s)'
  l.Add, 'RETURN, REFORM(d, ' + dims + ')'
  l.Add, 'END'
l.Add, 'PRO ' + className + '__DEFINE'
l.Add, '!null = {' + className + ', INHERITS IDL_Object}'
l.Add, 'END'
Clipboard.Set, l.ToArray(/NO_COPY)

Copy, save, and compile the source shown above.

Execute the routine giving it the name of a directory containing image files and the name of the object class to generate.  For example,

IDL> imagestostaticclass, FILEPATH('', SUBDIR=['resource','bitmaps']), 'IDLBitmaps'

Paste the routine's output to an editor file and save it as idlbitmaps__define.pro, then compile it.

To create an "open folder" bitmap widget button, for example, I can simply specify a call to the static method whose name corresponds to the original image file name, modulo a call to IDL_VALIDNAME.


Many of the bitmap files in IDL's resource directory are palletized, single plane images each with an associated color look-up table. For the sake of expediency and code simplicity at the expense of code size in ASCII characters, they're converted by the utility from single-plane images to RGB format before compressing them.

Notice that the utility skips any files which cannot be read successfully by the READ_IMAGE function. Other than that there is no robust error checking, an exercise left for the reader.

The routine returns the images dimensioned [x,y,3], the format required by WIDGET_BUTTON.

Comments (0) Number of views (2083) Article rating: No rating

Categories: IDL Data Point





Dynamic Keyword Validation using ROUTINE_INFO

Author: Brian Griglak

  In IDL, there are three variants of the _EXTRA keyword: _EXTRA, _REF_EXTRA, and _STRICT_EXTRA.  The _EXTRA and _REF_EXTRA keywords are used to define the signature of the routine, while you can use either _EXTRA or _STRICT_EXTRA when you invoke any routine.  As my colleague Jim Pendleton wrote about a few years ago, the _REF_EXTRA keyword is a potential improvement over plain old _EXTRA.  This is because it uses pass by reference semantics instead of the pass by value semantics that _EXTRA does, so you don’t make copies of the variables at each function call.  There is the downside that the values passed by _REF_EXTRA are mutable, so some care needs to be taken there, but it can save you a lot of memory and time allocating those copies.

  The downside of both _EXTRA and _REF_EXTRA is that they are generic grab bags that allow the caller to call your function with any superfluous keywords they want, with no way of knowing which keywords are actually used and which are extraneous.  Enter the _STRICT_EXTRA keyword, which is discussed in the help documentation of “Keyword Inheritance”.  Normally when you call a routine, you use _EXTRA keyword as the way to pass in the collection of keywords to the routine.  Any keywords in the _EXTRA bag that are part of the routine signature will be properly mapped, and whatever is left will be captured by _EXTRA/_REF_EXTRA if it’s present, or dropped on the floor otherwise.  If you were to call the same routine using _STRICT_EXTRA instead of _EXTRA, then it will throw an error if there are any keywords that do not map to the routine signature.  We can see this in action in this contrived example, which uses minimal procedures to illustrate the validation logic:

pro callByValue, _EXTRA=extra
  help, extra
pro callByReference, _REF_EXTRA=refExtra
  help, refExtra
pro callWithNoExtra, FOO=foo
  help, foo

pro callWrapper, _REF_EXTRA=extra
  callByValue, _EXTRA=extra
  callByReference, _EXTRA=extra
  callWithNoExtra, _EXTRA=extra
pro callStrictWrapper, _REF_EXTRA=extra
  callByValue, _STRICT_EXTRA=extra
  callByReference, _STRICT_EXTRA=extra
  callWithNoExtra, _STRICT_EXTRA=extra
pro extra_tests, _REF_EXTRA=extra
  callWrapper, PI=!pi
  callStrictWrapper, PI=!pi


The output from running extra_tests is:
** Structure <1307e5a0>, 1 tags, length=4, data length=4, refs=1:
   PI              FLOAT           3.14159
REFEXTRA        STRING    = Array[1]
FOO             UNDEFINED = <Undefined>
** Structure <1307e650>, 1 tags, length=4, data length=4, refs=1:
   PI              FLOAT           3.14159
REFEXTRA        STRING    = Array[1]
% Keyword PI not allowed in call to: CALLWITHNOEXTRA
% Execution halted at: CALLSTRICTWRAPPER   32 C:\Users\brian\IDLWorkspace83\Default\extra_tests.pro
%                      EXTRA_TESTS        43 C:\Users\brian\IDLWorkspace83\Default\extra_tests.pro
%                      $MAIN$

  The _EXTRA and _REF_EXTRA keywords are able to accept the PI keyword without incident.  When callWrapper invokes callWithNoExtra, the PI keyword is dropped on the floor, and FOO is left undefined.  But when callStrictWrapper invokes callWithNoExtra, the PI keyword does not line up with the routine signature and an error is thrown.  It is important to note that callByValue and callByReference work perfectly fine with _STRICT_EXTRA, as their _EXTRA and _REF_EXTRA keywords respectively will swallow up the PI keyword no problem.

  So _STRICT_EXTRA has its limitations, particularly in the case where you want to call more than one routine that doesn’t include a form of _EXTRA.  To accomplish this we need to query the IDL runtime to get information about the routine signatures using the powerful ROUTINE_INFO function.  When you use this function with its /PARAMETERS keyword, it will return a structure that lets you get the list of all the keywords in a given routine’s signature.  Let’s look at a simple example to see what ROUTINE_INFO returns:

pro myPro, PARAM1=p1, PARAM2=p2, PARAM3=p3
  print, 'in myPro'
  help, p1, p2, p3

function myFunc, PARAM2=p2, PARAM4=p4
  print, 'in myFunc'
  help, p2, p4
  return, 0

IDL> info1 = Routine_Info('myPro', /PARAMETERS)
IDL> info2 = Routine_Info('myFunc', /PARAMETERS, /FUNCTIONS)
IDL> info1
    NUM_ARGS: 0,
    NUM_KW_ARGS: 3,
IDL> info2
    NUM_ARGS: 0,
    NUM_KW_ARGS: 2,
    KW_ARGS: ["PARAM2" "PARAM4"]

  The implied print output for a struct is convenient as it gives you the tags, but also expands arrays, which neither help nor print will do for a struct.  You’ll notice that I had to add the /FUNCTIONS keyword when I was requesting info about a function instead of a procedure.  So I can use the KW_ARGS member of the info struct to identify which members of the _REF_EXTRA bag to use for each routine invocation.  I can also use it to verify that there aren’t any superfluous keywords passed into the wrapper.

  Once you’ve verified that the keywords passed into the wrapper are valid, you then need to construct the subset of values that are to be passed into each wrapped routine.  The way to do this dynamically is to manually construct a struct to pass into the _EXTRA keyword.  We do this incrementally by adding only those keywords and values to a struct using CreateStruct to append new key/value pairs.  When using _REF_EXTRA, you get a string array that includes the keywords used to invoke your method.  To get the values for each keyword you have to use Scope_VarFetch with its /REF_EXTRA keyword to get a reference to that value in the local scope.  Here is an example wrapper for the myPro and myFunc routines defined above:

function myDynamicReferenceWrapper, _REF_EXTRA=refExtra
  compile_opt idl2
  ; first make sure _REF_EXTRA is defined, bail if not
  if (~ISA(refExtra)) then return, -1

  info1 = Routine_Info('myPro', /PARAMETERS)
  info2 = Routine_Info('myFunc', /PARAMETERS, /FUNCTION)

  ; check for invalid keywords in _REF_EXTRA
  foreach keyword, refExtra do begin
    if ((Total(keyword eq info1.KW_ARGS) eq 0) && $
        (Total(keyword eq info2.KW_ARGS) eq 0)) then begin
      Message, 'Invalid keyword ' + keyword

  ; call myPro with the appropriate keywords from _REF_EXTRA
  extra1 = {}
  foreach keyword, info1.KW_ARGS do begin
    w = where(keyword eq refExtra, found)
    if (found gt 0) then begin
      value = Scope_VarFetch(keyword, /REF_EXTRA)
      extra1 = Create_Struct(extra1, keyword, value)
  myPro, _EXTRA=extra1

  ; call myFunc with the appropriate keywords from _REF_EXTRA
  extra2 = {}
  foreach keyword, info2.KW_ARGS do begin
    w = where(keyword eq refExtra, found)
    if (found gt 0) then begin
      value = Scope_VarFetch(keyword, /REF_EXTRA)
      extra2 = Create_Struct(extra2, keyword, value)
  return, myFunc(_EXTRA=extra2)

  If you had concerns about the wrapped methods modifying your variables and want to use _EXTRA instead of _REF_EXTRA, then there are a couple tweaks to this wrapper:

function myDynamicValueWrapper, _EXTRA=extra
  compile_opt idl2
  ; first make sure _EXTRA is defined, bail if not
  if (~ISA(extra)) then return, -1

  info1 = Routine_Info('myPro', /PARAMETERS)
  info2 = Routine_Info('myFunc', /PARAMETERS, /FUNCTIONS)

  ; check for invalid keywords in _EXTRA
  myExtraKeywords = Tag_Names(extra)
  foreach keyword, myExtraKeywords do begin
    if ((Total(keyword eq info1.KW_ARGS) eq 0) && $
        (Total(keyword eq info2.KW_ARGS) eq 0)) then begin
      Message, 'Invalid keyword ' + keyword

  ; call myPro with the appropriate keywords from _EXTRA
  extra1 = {}
  foreach keyword, info1.KW_ARGS do begin
    w = where(keyword eq myExtraKeywords, found)
    if (found gt 0) then begin
      extra1 = Create_Struct(extra1, keyword, extra.(w[0]))
  myPro, _EXTRA=extra1

  ; call myFunc with the appropriate keywords from _EXTRA
  extra2 = {}
  foreach keyword, info2.KW_ARGS do begin
    w = where(keyword eq myExtraKeywords, found)
    if (found gt 0) then begin
      extra2 = Create_Struct(extra2, keyword, extra.(w[0]))
  return, myFunc(_EXTRA=extra2)

  Either of these wrappers could be made truly generic by adding string array parameter that was the set of routine names you want wrapped, instead of having myPro and myFunc hardcoded.

Comments (0) Number of views (187) Article rating: No rating

Categories: IDL Data Point





Dynamic Plots Using an Equation String

Author: Nancy Rynes

The PLOT function's new Equation argument adds flexibility to the creation of plots in IDL, allowing you to create dynamic, interactive output.

The Equation argument on the PLOT function allows you to specify either a string containing an equation with variable X, or the name of an IDL function that accepts X as an input argument. The result of the equation (or the function) should be a one-dimensional array of Y coordinates to be plotted.

  • If Equation is an expression, then the EXECUTE function is called once with the X array. Note that in certain circumstances (such as the IDL Virtual Machine), you may not be able to use the EXECUTE function.
  • If Equation is a function name, then CALL_FUNCTION is called once, with the X array as an input argument. The function should return a one-dimensional result array.

Once IDL creates the plot output, if you then interactively adjust the plot range, IDL will automatically recompute the equation to cover the new range.

We'll use the BESELJ function in IDL to show how to use a Function String in the Equation Argument:


; Plot J Bessel Functions

pj0 = PLOT('BESELJ(X, 0)', XRANGE=[0.0, 50],  $

  XTITLE='X', YTITLE='$J_n(x)$ or $Y_n(x)$', $

  TITLE='J Bessel Function')

pj1 = PLOT('BESELJ(X, 1)', 'r2', XRANGE=[0.0, 50], /OVERPLOT)

pj2 = PLOT('BESELJ(X, 2)', 'b2', XRANGE=[0.0, 50], /OVERPLOT)

; Annotate the plot.

xcoords = [1, 1.66, 3]

ycoords = [.8, .62,.52]

labels = '$\it' + ['J_0','J_1','J_2'] + '$'

  t = TEXT(xcoords, ycoords, labels, /DATA)


If you run the code above, it should generate a graphic like this:

Once IDL creates the plot, test out its dynamic capabilities:

  • Try clicking with the middle mouse button on the graphic and panning around.
  • You can also use the mouse wheel to zoom in or out, or hold down the <Shift> key and draw a zoom box.
  • Change the plot range programatically at the IDL command line:

pj0.xrange=[0.0, 150]

As you change the plot range, IDL recomputes the equation with new X values that span the new range.

Comments (0) Number of views (217) Article rating: No rating

Categories: IDL Data Point





Resizable Widgets

Author: Benjamin Foreback

When developing widgets, it can often be tricky to position them correctly and make them the right size. Even for an experienced programmer, it takes quite a bit of trial-and-error. Code can quickly become messy and difficult to read when sizing code is mixed in with widget creation; this is especially true when developing a cross-platform widget, which may need different sizes depending on the platform.

Here is an example of widget code that does not contain any size definitions:

  base = WIDGET_BASE(/COLUMN, TITLE='Please make some selections', $
  features_label = WIDGET_LABEL(base, VALUE='Check all that apply:')
  features_base = WIDGET_BASE(base, /ROW, /NONEXCLUSIVE, UNAME='features_base')
  glasses = WIDGET_BUTTON(features_base, VALUE='Wears glasses', UNAME='glasses')
  moustache = WIDGET_BUTTON(features_base, VALUE='Moustache', UNAME='moustache')
  over6ft = WIDGET_BUTTON(features_base, VALUE='Taller than 6ft', UNAME='over6ft')

  haircolor_label = WIDGET_LABEL(base, VALUE='Select Hair Color:')
  haircolor_base = WIDGET_BASE(base, /ROW, /EXCLUSIVE, UNAME='haircolor_base')
  black = WIDGET_BUTTON(haircolor_base, VALUE='Black', UNAME='black')
  brown = WIDGET_BUTTON(haircolor_base, VALUE='Brown', UNAME='brown')
  blond = WIDGET_BUTTON(haircolor_base, VALUE='Blond', UNAME='blond')
  red = WIDGET_BUTTON(haircolor_base, VALUE='Red', UNAME='red')

  descrip_label = WIDGET_LABEL(base, VALUE='Describe yourself:')
  descrip = WIDGET_TEXT(base, /EDITABLE, UNAME='descrip')

  bottom_base = WIDGET_BASE(base, /ROW, UNAME='bottom_base')
  help_base = WIDGET_BASE(bottom_base, /ROW, UNAME='help_base')
  help = WIDGET_BUTTON(help_base, VALUE='Help', UNAME='help')
  space = WIDGET_BASE(bottom_base, UNAME='space')
  commit_base = WIDGET_BASE(bottom_base, /ROW, UNAME='commit_base', /GRID_LAYOUT)
  ok = WIDGET_BUTTON(commit_base, VALUE='OK', UNAME='ok')
  cancel = WIDGET_BUTTON(commit_base, VALUE='Cancel', UNAME='cancel')

This is what the widget looks like after calling WIDGET_CONTROL, base, /REALIZE on the widget (on Windows 7):

This doesn't look bad. However, the OK and Cancel buttons are not in their usual spot, the bottom-right corner, and the text box is rather small.

One trick to creating a widget that has the desired size and layout is to write a separate routine instead of modifying the code above. Once the above code is run, simply call the new routine with the desired size before realizing the base.

Why do this in a separate routine? This routine will not only set the original size, but it can also be used to resize the widget and all of its components when the user drags the edges or corners. Allowing the user to resize the widget will provide an enhanced user-experience. In this example, the user may enter text that is longer than the width of the text box, and the ability to widen it would allow the user to view all of the text at once rather than using the arrow keys to navigate through it.

Here is an example of a routine that can be used to adjust the size of this example widget:

PRO example_widget_adjust_size, base, new_size

  ; Do not allow the widget to be made any smaller than the original size.
  WIDGET_CONTROL, base, GET_UVALUE=orig_size
  IF new_size[0] LT orig_size[0] THEN new_size[0] = orig_size[0]
  IF new_size[1] LT orig_size[1] THEN new_size[1] = orig_size[1]

  ; Determine the padding, which needs to be subtracted.
  base_geom = WIDGET_INFO(base, /GEOMETRY)
  xpad = base_geom.xpad
  ypad = base_geom.ypad

  ; There is padding on both sides of the textbox, so subtract twice the xpad.
  descrip_width = new_size[0] - 2*xpad
  descrip = WIDGET_INFO(base, FIND_BY_UNAME='descrip')
  WIDGET_CONTROL, descrip, SCR_XSIZE=descrip_width

  ; Determine the size of the Help, OK and Cancel buttons. There are
  ; three bases within the bottom base, so subtract 4 times xpad.

  commit_base = WIDGET_INFO(base, FIND_BY_UNAME='commit_base')
  commit_geom = WIDGET_INFO(commit_base, /GEOMETRY)
  help_base = WIDGET_INFO(base, FIND_BY_UNAME='help_base')
  help_geom = WIDGET_INFO(help_base, /GEOMETRY)
  space_width = new_size[0] - commit_geom.xsize - help_geom.xsize - 4*xpad
  space = WIDGET_INFO(base, FIND_BY_UNAME='space')
  WIDGET_CONTROL, space, XSIZE=space_width


The main routine now needs to define a size and pass it, along with the base, into this routine. There are two options for choosing the original size: A fixed original size can be chosen, based on what the developer thinks is suitable, or the original size can be calculated by calling WIDGET_INFO with /GEOMETRY on each of the component items and totaling the x and y sizes. For example purposes, a fixed size will be used. When using a fixed original size, it may be a good idea to make the size slightly larger on motif widgets than on Windows because motif widgets use bigger text. 

Here is the code needed to set the original size, which should be added right above the REALIZE call:

    orig_size = [300, 180]
    orig_size = [350, 200]

  ; Set the original size in the UVALE to keep track of it.
  WIDGET_CONTROL, base, SET_UVALUE=orig_size
  example_widget_adjust_size, base, orig_size

If the developer decides to change the original size, this is the only place that will need to be updated. Here is the new look:

Allowing the user to resize the widget now falls into place. The widget's event handler can catch the size event, which uses the WIDGET_BASE structure, and pass the new size into the resize routine.

    example_widget_adjust_size, event.top, [event.x, event.y]

Now the user can expand the widget, and the text box and buttons automatically adjust:

In this example, the labels, checkboxes, and radio buttons aren't changed when the widget is resized, but this technique can be applied to any widget, no matter how big or small, in order to provide a resizable widget.

Comments (0) Number of views (234) Article rating: No rating

Categories: IDL Data Point


12345678910 Last




© 2014 Exelis Visual Information Solutions