IDL 5220 Week 7 - Program Design and Control



  1. Mouse Clicks and Cursor Position:
    When you create a draw widget, you can enable mouse events (see Widgets). If mouse events are enabled, you can detect which button was pressed/released. First, you'll want to check if the detected widget event occured in the draw widget. You can do this by checking the uvalue of the widget that generated the event:
    widget_control,ev.id,get_uvalue=uval
    if uval eq 'draw' then begin...
    Mouse events in draw widgets have properties press and release that receive a value of 1 if it was the left mouse button, 2 for the center button, and 4 for the right button:
    if ev.press eq 1 then print,'You clicked the left mouse button.'
    if ev.release eq 4 then print,'You released the right mouse button.'
    
    Now you might want to know the location of the mouse pointer when the press or release occurred. This can be obtained by the cursor command:
    cursor,x,y,/nowait,/data
    x and y are variables that will receive the x and y coordinates of the mouse pointer. /nowait specifies that the coordinates should be read immediately, and you can specify /data for data coordinates or /normal for normalized coordinates (just like in XYOUTS).
  2. System Variables:
    Most system properties can be set through system variables. You have actually used a system variable before: !P.multi. !P is a structure that is a system variable. Structures are datatypes that contain multiple items, like arrays, except that they can hold different datatypes within them, and the individual items can have IDs that reference them. For instance !P could look like: { 0 [0,0,1,1] [0,1,1,0,0] } with the third item being the !P.multi array. Since IDs can be assigned to each item though, the structure could maybe be better thought of as looking like { window:0 position:[0,0,1,1] multi:[0,1,1,0,0] }. Because of this, you don't need to know that multi is the third item in !P. You can reference it by its id: !P.multi.

    Other than !P, there is also !X and !Y (and !Z) which contain most of the properties of the x and y (and z) axes. For instance setting !X.ticks=5 is the same as setting xticks=5 in a call to the PLOT procedure. Similarly you could set !Y.margin=[2,4]. !D controls the display. All system variables can be looked up in ? and can be very useful in more advanced programs.
  3. Designing programs:
    A huge part of any program should be done before the first line of code is entered. Whether its very organized and neatly mapped out, or as I usually do, mostly think it out in my head and jot down a few notes about variables, methods, and some snippets of code and pseudo-code, the following things should be planned out before you begin programming: algorithms, methods, quantities that need to be represented by variables or arrays, the preferred data structures, certain loops and conditionals related to the algorithms, possible errors, time complexity, and program control.
  4. Data Structures:
    I really haven't talked much about data structures in IDL because it is not an object oriented language. Usually arrays are the best choice for IDL. But that is not always the case. Sometimes structures would be better. And in other languages you may encounter structures such as stacks, queues, linked lists, and trees. You have to determine which data stucture would be best for your program. Stacks only allow you to push a value onto the top of the stack and pop the top value off of the stack. Queues allow you to insert data only at the back of the queue and remove data only at the front of the queue so that the data remains in first in first out order (FIFO queues are used when you burn CDs for instance). Linked lists contain a reference to the memory location of the next object in the sequence. Trees are traversable data structures where each object (or node) can have several subnodes. They can be very efficient but are the most difficult of the four to set up. Here is an example of a stack, but I won't go more into it because this isn't a java class:
    class Stack {
       private int[] numbers;
       private int n;
       public Stack() {
          this.numbers=new int[100];
          this.n=0;
       }
       public void push(int x) {
          numbers[n]=x;
          n++;
       }
       public int pop() {
          int x = numbers[n-1];
          n--;
          return x;
       }
    }
    This stack can be initialized by: Stack s = new Stack(); Then numbers can be added by: s.push(5); or removed by x=s.pop(); Obviously, an error will be generated if I try to add 101 numbers or remove a number from an empty stack, but I wanted to keep this simple. If I were really writing a stack, I would include error handling routines that would notify the user if they tried to do a pop on an empty stack, but not cause an error, and one that would increase the size of the stack on the fly if a user does a push on a full stack. Anyone that has used the HP48 calculators should be familiar with a stack. This is also a good example of how object oriented programming differs from sequential programming. It can be much more flexible, but requires a different mindset to write programs.
  5. Planning:
    Before you start your program, the first thing you need to do is develop your algorithm(s). You need to figure out how to solve the problem. It will generally be an existing algorithm, but could be a new one. For instance if your program was to sort objects by their RA and Dec, you may want to use the Bubble Sort as your algorithm. Or maybe you need to design your own custom loop. When designing a loop, you want to figure out the post-condition first: what must be true when the loop ends. Then you need to figure out a conditional that when falsified will cause the post-condition to happen and an invariant that will remain constant throughout the loop. Lastly, you have to make sure that you have a variant that will at some point cause the conditional to become false so you don't get trapped in an infinite loop. Once you decide on how you are going to approach the program, then you can start planning how you are going to implement it. Now you need to decide what type of data structures you are going to use and what quantities need to be represented by variables. You'll want to have FLTARRs to contain the RA and Dec (or maybe a 2-D FLTARR) and a STRARR for the object names. Now is the pseudo-code portion of planning. It is very helpful to write out in pseudo-code what you want the program to do:
    for j=0, # of objects-1 do begin
       for l=j+1, # of objects do begin
          if RA[j] gt RA[l] then swap RAs, Decs, names
       endfor
    endfor
    Now that you have this outline, you will know what other variables you are going to need, and you'll know the time complexity. In this case, you will need a variable for the # of objects, and the time complexity is order n^2: the time it takes to sort will be proportional to the square of the number of objects. By using a winner tree, you could improve this to order n*log(n), so you need to make a decision: if you are going to be handling smaller quantities of data, then stay with the easy-to-write bubble sort. However, if you are going to run the program many times on large quantities of data, it may be more efficient to spend the time in developing the code for a quick sort by using a winner tree because it will save a lot of time during execution.
  6. Methods:
    Methods are very important when designing programs. Whenever you are going to use a portion of code multiple times, you want to put it in a method that can be reuseable without rewriting any code. It is also much easier to write error-free programs if you break big tasks down into a series of much smaller, cleaner ones. In the example above, what if you want to sort at multiple places in the program, and maybe by Dec or name as well as RA? You could make your Bubble Sort routine into its own method:
    pro sortData, x, y, z
       for j=0,n_elements(x)-2 do begin
          for l=j+1,n_elements(x)-1 do begin
    	if x[j] gt x[l] then swap x's, y's, z's
          endfor
       endfor
    end
    
    Now you could call this routine by: sortData,RA,Dec,name if you want to sort by RA or sortData,name,RA,Dec if you want to sort by name (or of course sortData,Dec,RA,name). It is called with 3 arrays and it sorts based on the first one. Once you get this method working, you know it works and you don't have to worry about anything anymore (see also push and pop from the Stack example above). So there is no need for you to rewrite any of the code and if something isn't working, you know it is in the code that calls the method, not the method itself.
  7. Error Handling:
    Now that you have started writing your methods, you need to think of error handling. Try to think of anything that could go wrong (see the Stack example above) and provide an error handling routine to deal with each possible scenario. For instance in HW3, there is an option for deleting a record. When I write the code for deleting the record, I write it inside an IF block that checks to see if the requested record exists. If it doesn't, the ELSE code is executed which prints a message that the record was not found. As with the program, start small, and figure out anything that could possibly go wrong in a method. Once the method is able to handle any possible scenario, move onto the code that calls the method.
  8. Putting it Together:
    Once you have your program all planned out, then you can start writing it. The planning could have taken weeks of figuring out algorithms and writing pseudo-code, or seconds to think in your head that a Bubble Sort would be the best way to do something. But now you're ready to write the program. Start writing method by method. Don't try to write the whole thing top-down. Of course it is easier to do this in object oriented programming, which is by nature non-linear. I would write the class Stack, then write the code to use the Stack. In IDL it works the same way though. In HW5, I wrote the factorial function and interpolation procedures. Then I wrote the code that printed out the factorials of 1-10 and the code that read in a file, created a contour plot, interpolated, and created another contour plot. At that point, I knew the interpolation procedure would work, so I didn't even have to worry about it any more and could treat it the same as I would a built-in command.
  9. Program Control:
    Program control is about what method has control of the program when and for how long and what it does with it. It is much more applicable to object oriented programming of course, and in particular concurrent programming when you may have multiple threads of control trying to access the same things at the same time (in which case synchronization is required so that you don't get a big mess). It is still a good idea to keep in mind in IDL though. You also want to keep in mind any future additions you may want to make. For instance, GatorPlot started out as just a tool to create stacked plots of multiple spectra without having to rewrite code each time I wanted to do something a little differently. Because of this, after v1.0, I had to do a major redesign in the way a lot of things were handled. Now, if I had to redo it from scratch, I would handle some things differently to make it cleaner (especially regarding exporting files where the code can get a little messy), but for the most part, it is very easy to add almost any new features I want...it was very easy to add color and fitting for instance. I have a procedure readdata that takes a lot of paramaters. I just call this procedure and the data from all files is read into the correct variables. Similarly, the makeplot procedure make the plot. I can call this after setting the device to .PS and the plot is sent to a PS file or I can call it when someone clicks Integrate to draw the plot to the screen and allow the user to choose which part to integrate over. When someone clicks Smooth, the smoothplot procedure is called. Immediately after that, makeplot is called, which plots the now smoothed data. If I wanted to add some feature that would manipulate the data in a new way and plot it, I would simply have to write a procedure that would do the new data manipulation. Then I could call readdata then the new procedure then makeplot. So it is a good idea to break tasks down into smaller methods, and keep track of the control flow of your program.

Homework 7: Simple Interactive Plotting Package. Design an interactive plotting package that will plot FITS files. Your GUI should include a 640 x 512 draw widget, a text field to enter a filename, as well as a button which pops up a file selection dialog, and text fields to enter X and Y axis titles, as well as X and Y ranges. There should be a Plot button that generates the plot, and an Export JPEG button that exports the plot currently displayed on the screen to a JPEG image. When the Export JPEG button is clicked, it should bring up a window that asks for a filename and has Export and Cancel buttons. Finally, there should be a quit button. Use Xmanager to register your base widget and handle widget events, although you may handle events manually with widget_event for the Export widget. If any part of the X or Y range is not entered into the text field, it should default to the corresponding min/max of the X/Y values. My version of the program is only 77 lines and took only 40 minutes to write. Everything required to write the program is in the notes for this and past weeks.