; docformat = 'rst'
;
; NAME:
;   cgZPlot__Define
;
; PURPOSE:
;   This program creates a "zoomable" line plot in an interactive window. The user can
;   zoom into or out of the plot. Once a plot is zoomed, the user can then pan the plot
;   in both the X and Y directions. See the operating instructions for how to interact
;   with the line plot.
;
;******************************************************************************************;
;                                                                                          ;
;  Copyright (c) 2012, by Fanning Software Consulting, Inc. All rights reserved.           ;
;                                                                                          ;
;  Redistribution and use in source and binary forms, with or without                      ;
;  modification, are permitted provided that the following conditions are met:             ;
;                                                                                          ;
;      * Redistributions of source code must retain the above copyright                    ;
;        notice, this list of conditions and the following disclaimer.                     ;
;      * Redistributions in binary form must reproduce the above copyright                 ;
;        notice, this list of conditions and the following disclaimer in the               ;
;        documentation and/or other materials provided with the distribution.              ;
;      * Neither the name of Fanning Software Consulting, Inc. nor the names of its        ;
;        contributors may be used to endorse or promote products derived from this         ;
;        software without specific prior written permission.                               ;
;                                                                                          ;
;  THIS SOFTWARE IS PROVIDED BY FANNING SOFTWARE CONSULTING, INC. ''AS IS'' AND ANY        ;
;  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES    ;
;  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT     ;
;  SHALL FANNING SOFTWARE CONSULTING, INC. BE LIABLE FOR ANY DIRECT, INDIRECT,             ;
;  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED    ;
;  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;         ;
;  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND             ;
;  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT              ;
;  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS           ;
;  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                            ;
;******************************************************************************************;
;+
;  This program creates a "zoomable" line plot in an interactive, resizable window. The user 
;  can zoom into or out of the plot. Once a plot is zoomed, the user can then pan the plot
;  in both the X and Y directions. See the operating instructions for how to interact
;  with the line plot.
;  
;  Operating Instructions--
;  
;  Use the LEFT mouse button to zoom the plot and the RIGHT mouse button to pan the plot.
;  
;  If you click and drag inside the plot axes, you will create a rubber band box. Select the
;  portion of data you wish to zoom into. The zoom will occur in both the X and Y directions.
;  If you wish to zoom the plot all the way back out, simply click and release the LEFT mouse
;  button inside the plot axes without moving the mouse.
;  
;  Once you are zoomed into a plot, you can adjust the zoom by clicking the LEFT mouse button
;  outside the plot axes. If you click the mouse below the plot, you will cause the X axis to
;  zoom out of the plot by the zoomFactor amount (normally 5% of the current range of the axis).
;  If you wish to zoom the X axis into the plot, simply click above in the region of the window
;  above the plot. Click below the plot to zoom out, above the plot to zoom in. Similarly, you 
;  can adjust the zoom on the Y axis. Clicking to the left of the plot zooms the Y axis out, 
;  while clicking to the right of the plot zooms the Y axis in.
;  
;  If you are zoomed into the plot, you can pan to different locations in the plot by using
;  the RIGHT mouse button. Hold and drag the RIGHT mouse button inside the plot axes. The
;  entire plot will pan in both the X and Y directions.
;  
;  File output requires that ImageMagick and GhostScript be installed on your machine. Note
;  that exact axis scaling is always in effect.
;
;  The program requires the `Coyote Library <http://www.idlcoyote.com/documents/programs.php>`
;  to be installed on your machine.
;        
; :Categories:
;    Graphics
;    
; :Examples:
;    Plot examples::
;       cgZPlot, cgDemodata(1), PSYM=2, Color='dodger blue'
;       
;    To put this in your own widget program::
;        tlb = Widget_Base(Title='My Program')
;        cgZPlot, cgDemodata(1), PSYM=2, Color='dodger blue', Parent=tlb
;        Widget_Control, tlb, /Realize
;        Widget_Control, 'myprogram', tlb, /NoBlock
;    
; :Author:
;    FANNING SOFTWARE CONSULTING::
;       David W. Fanning 
;       1645 Sheely Drive
;       Fort Collins, CO 80526 USA
;       Phone: 970-221-0438
;       E-mail: david@idlcoyote.com
;       Coyote's Guide to IDL Programming: http://www.idlcoyote.com
;
; :Copyright:
;     Copyright (c) 2012, Fanning Software Consulting, Inc.
;     
; :History:
;     Change History::
;        Written 16 May 2012, by David W. Fanning.
;        Added UNDO capability arbitrarily set to 50 items. 17 May 2012. DWF.
;        Added a REDO capability and the ability to adjust the Y range (via a button) so that 
;           you can see the actual data Y range of all the data in the X range of a particular
;           view of the data. 21 May 2012. DWF.
;        Added a PARENT keyword and changed the algorithm slightly so that this
;           interactive widget functionality can be incorporated into your own
;           widget programs. 21 may 2012. DWF.
;        Added compile options idl2 to all modules. Fixed a typo for REDO button. 14 June 2012. DWF.
;        Separated the object code from the driver code for easier inheritance. 14 June 2012. DWF.
;        Removed the POLAR keyword, which can't be used in a zoom plot. 15 June 2012. DWF.
;        Added a persistent output save directory.  30 June 2012. DWF.
;        Added an ERASE method to erase the current display. 10 July 2012. DWF.
;        Added a LABEL keyword to add a label instead of a title to a plot. 13 July 2012. DWF.
;        Added the ability to include overplot objects in the zoom window. 17 July 2012. DWF.
;        Added a Destroy method and now remove widget GUI in CLEANUP method. 2 Oct 2012. DWF.
;        I had the GetProperty and SetProperty keyword inheritance mechanism screwed up. Sorted
;           now. 13 March 2013. DWF.
;-

;+
; This is the initialization method of the cgZPlot object. In general, any keyword appropriate
; for the cgPlot command can be used with this program.
; 
; :Params:
;    x: in, required, type=any
;         If X is provided without Y, a vector representing the dependent values to be 
;         plotted If both X and Y are provided, X is the independent parameter and 
;         Y is the dependent parameter to be plotted.
;    y: in, optional, type=any
;         A vector representing the dependent values to be plotted.
;       
; :Keywords:
;     aspect: in, optional, type=float, default=none
;        Set this keyword to a floating point ratio that represents the aspect ratio 
;        (ysize/xsize) of the resulting plot. The plot position may change as a result
;        of setting this keyword. Note that `Aspect` cannot be used when plotting with
;        !P.MULTI.
;     label: in, optional, type=string
;        A label is similar to a plot title, but it is aligned to the left edge
;        of the plot and is written in hardware fonts. Use of the label keyword
;        will suppress the plot title.
;     legends: in, optional, type=object
;         A single cgLegendItem object, or an array of cgLegendItem objects that will be
;         drawn on the plot as a legend.
;     max_value: in, optional, type=float
;        Set this keyword to the maximum value to plot. Any values greater than this 
;        value are treated as missing.
;     min_value: in, optional, type=float
;        Set this keyword to the minimu value to plot. Any values smaller than this 
;        value are treated as missing.
;     oplots: in, optional, type=object
;         A single cgOverPlot object, or an array of cgOverPlot objects that will be
;         overplot on the axes set up by the original data.
;     parent: in, optional, type=long
;        The identifer of the parent widget for this program's draw widget. If not
;        provided, the program will create it's own top-level base widget as a parent.
;     xlog: in, optional, type=boolean, default=0
;        Set this keyword to use a logarithmic X axis
;     xrange: in, optional, type=double
;        Set this keyword to a two-element array giving the X data range of the plot.
;     xsize: in, optional, type=int, default=640
;        The X size of the program's draw widget.
;     ylog: in, optional, type=boolean, default=0
;        Set this keyword to use a logarithmic Y axis
;     ynozero: in, optional, type=boolean, default=0
;        Set this keyword to use allow the Y axis to start at a value other than zero.
;     yrange: in, optional, type=double
;         Set this keyword to a two-element array giving the Y data range of the plot.
;     ysize: in, optional, type=int, default=512
;         The Y size of the program's draw widget.
;     zoomfactor: in, optional, type=float
;         Set this keyword to a number between 0.01 and 0.25. This affects the amount
;         of zooming when the X axis and Y axis are zoomed with the LEFT mouse button.
;         The default value is 0.05 or five percent of the current axis range on each
;         end of the axis, resulting in a 10 percent change in the axis length.
;     _ref_extra: in, optional, type=any
;        Any keyword appropriate for the IDL Plot or Coyote Graphic cgPlot command is 
;        allowed in the program. Note that this is not the same as saying it is a good
;        idea to use every one of the these keywords. Use good judgement.
;-
FUNCTION cgZPlot::INIT, x, y, $
    ASPECT=aspect, $
    DRAWID=drawid, $
    LABEL=label, $
    LEGENDS=legends, $
    MAX_VALUE=max_value, $
    MIN_VALUE=min_value, $
    OPLOTS=oplots, $
    PARENT=parent, $
    XLOG=xlog, $
    XRANGE=xrange, $
    XSIZE=xsize, $
    YLOG=ylog, $
    YRANGE=yrange, $
    YNOZERO=ynozero, $
    YSIZE=ysize, $
    ZOOMFACTOR=zoomfactor, $
    _REF_EXTRA=extra
    
    Compile_Opt idl2
    
    Catch, theError
    IF theError NE 0 THEN BEGIN
        Catch, /Cancel
        void = cgErrorMsg()
        RETURN, 0
    ENDIF
    
    ; Set parameters and arguments.
    IF N_Elements(zoomfactor) EQ 0 THEN zoomfactor = 0.05 ELSE zoomfactor = 0.01 > zoomfactor < 0.25
    
    ; Call the superclass object INIT method.
    IF ~self -> cgGraphicsKeywords::INIT(_STRICT_EXTRA=extra) THEN RETURN, 0   
    
    ; Sort out which is the dependent and which is independent data.
    CASE N_Params() OF
      
       1: BEGIN
       dep = x
       indep = Findgen(N_Elements(dep))
       ENDCASE
    
       2: BEGIN
       dep = y
       indep = x
       ENDCASE
    
    ENDCASE
    
    ; If either of these input vectors are scalars, make them vectors.
    IF N_Elements(dep) EQ 1 THEN dep = [dep]
    IF N_Elements(indep) EQ 1 THEN indep = [indep]
    
    ; Allocate heap for variables.
    self.indep = Ptr_New(/Allocate_Heap)
    self.dep = Ptr_New(/Allocate_Heap)
    self.aspect = Ptr_New(/Allocate_Heap)
    self.max_value = Ptr_New(/Allocate_Heap)
    self.min_value = Ptr_New(/Allocate_Heap)
    self.xlog = Ptr_New(/Allocate_Heap)
    self.ylog = Ptr_New(/Allocate_Heap)
    self.ynozero = Ptr_New(/Allocate_Heap)
    self.undoList = Obj_New('LinkedList')
    self.redoList = Obj_New('LinkedList')
    IF N_Elements(oplots) NE 0 THEN self.oplots = Ptr_New(oplots) ELSE self.oplots = Ptr_New(/ALLOCATE_HEAP)
    IF N_Elements(legends) NE 0 THEN self.legends = Ptr_New(legends) ELSE self.legends = Ptr_New(/ALLOCATE_HEAP)
    
    self -> SetProperty, $
        INDEP=indep, $
        DEP=dep, $
        ASPECT=aspect, $
        MAX_VALUE=max_value, $
        MIN_VALUE=min_value, $
        XLOG=xlog, $
        XRANGE=xrange, $
        YLOG=ylog, $
        YRANGE=yrange, $
        YNOZERO=ynozero
        
    ; Set the draw widget size.
    IF N_Elements(xsize) EQ 0 THEN self.xsize = 640 ELSE self.xsize = xsize
    IF N_Elements(ysize) EQ 0 THEN self.ysize = 512 ELSE self.ysize = ysize
    
    ; Do you need to create your own TLB, or will you be using someone else's?
    IF N_Elements(parent) EQ 0 THEN BEGIN
        self.tlb = Widget_Base(Title='Zoom/Pan Plot', TLB_SIZE_EVENTS=1, $
           UVALUE={method:'TLB_RESIZE_EVENTS', object:self}, MBar=menuID)
           
        ; Menu items.
        fileID = Widget_Button(menuID, Value='File')
        output = Widget_Button(fileID, Value='Save As...', /Menu)
        button = Widget_Button(output, Value='BMP File', UVALUE={method:'FileOutput', object:self})
        button = Widget_Button(output, Value='EPS File', UVALUE={method:'FileOutput', object:self})
        button = Widget_Button(output, Value='GIF File', UVALUE={method:'FileOutput', object:self})
        button = Widget_Button(output, Value='JPEG File', UVALUE={method:'FileOutput', object:self})
        button = Widget_Button(output, Value='PDF File', UVALUE={method:'FileOutput', object:self})
        button = Widget_Button(output, Value='PS File', UVALUE={method:'FileOutput', object:self})
        button = Widget_Button(output, Value='PNG File', UVALUE={method:'FileOutput', object:self})
        button = Widget_Button(output, Value='TIFF File', UVALUE={method:'FileOutput', object:self})
        
        button =  Widget_Button(fileID, Value='Undo', ACCELERATOR="Ctrl+U", $
            UVALUE={method:'Undo', object:self}, /Separator)
        button =  Widget_Button(fileID, Value='Redo', ACCELERATOR="Ctrl+R", $
            UVALUE={method:'Redo', object:self})
            
        button = Widget_Button(fileID, Value='Adjust Range to Data Viewed', ACCELERATOR="Ctrl+A", $
            UVALUE={method:'AdjustRange', object:self}, /Separator)
        
        button =  Widget_Button(fileID, Value='Quit', UVALUE={method:'Quit', object:self}, /Separator)
    ENDIF ELSE self.tlb = parent
    
    ; Create the draw widget and pixmap. These are the essential elements of this object
    ; and should work in any parent widget.
    retain = (StrUpCase(!Version.OS_Family) EQ 'UNIX') ? 2 : 1
    self.drawID = Widget_Draw(self.tlb, XSize=self.xsize, YSize=self.ysize, $
       UVALUE={method:'BUTTON_EVENTS', object:self}, $
       RETAIN=retain, Button_Events=1, $
       NOTIFY_REALIZE='cgzplot_notify_realize', $
       EVENT_PRO='cgZplot_Events')
    Window, /Pixmap, /Free, XSize=self.xsize, YSize=self.ysize
    self.pixmapID = !D.Window
    
    ; Set object properties.
    self.drag = 0
    IF N_Elements(label) NE 0 THEN self.label = label
    IF N_Elements(xrange) EQ 0 THEN xrange = [Min(indep), Max(indep)]
    IF N_Elements(yrange) EQ 0 THEN yrange = [Min(dep), Max(dep)*1.05]
    self.orig_xrange = xrange
    self.orig_yrange = yrange
    self.zoomfactor = zoomfactor
    *self.xlog = Keyword_Set(xlog)
    *self.ylog = Keyword_Set(ylog)
    
    ; Must do exact axis scaling for smooth operation.
    IF N_Elements(*self.xstyle) NE 0 THEN *self.xstyle = *self.xstyle && 1 ELSE *self.xstyle = 1
    IF N_Elements(*self.ystyle) NE 0 THEN *self.ystyle = *self.ystyle && 1 ELSE *self.ystyle = 1
    
    ; Realize the widget and get it going, if you created the TLB.
    IF N_Elements(parent) EQ 0 THEN BEGIN
        Widget_Control, self.tlb, /Realize
        XManager, 'cgzplot', self.tlb, /No_Block, Event_Handler='cgZPlot_Events', $
           Cleanup='cgZPlot_Cleanup'
    ENDIF
    
    RETURN, 1
END

;+
; The clean-up method for the object. When the object is destroyed, 
; this method will free the object's pointers.
;-
PRO cgZPlot::CLEANUP

    Compile_Opt idl2
    
   ; Standard error handling.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      void = cgErrorMsg()
      RETURN
   ENDIF
   
    
    Ptr_Free, self.indep
    Ptr_Free, self.dep
    Ptr_Free, self.aspect
    Ptr_Free, self.max_value
    Ptr_Free, self.min_value
    Ptr_Free, self.xlog
    Ptr_Free, self.ylog
    Ptr_Free, self.ynozero
    Obj_Destroy, self.undoList
    Obj_Destroy, self.redoList
    WDelete, self.pixmapID
    
    ; Get rid of the overplot objects, if any.
    IF Ptr_Valid(self.oplots) THEN BEGIN
      FOR j=0,N_Elements(*self.oplots)-1 DO BEGIN
         Obj_Destroy, (*self.oplots)[j]
      ENDFOR
      Ptr_Free, self.oplots
    ENDIF

    ; Get rid of the legend objects, if any.
    IF Ptr_Valid(self.legends) THEN BEGIN
      FOR j=0,N_Elements(*self.legends)-1 DO BEGIN
         Obj_Destroy, (*self.legends)[j]
      ENDFOR
      Ptr_Free, self.legends
    ENDIF
    
    ; Call the superclass CLEANUP method.
    self -> cgGraphicsKeywords::CLEANUP
    
    ; If you have a valid TLB, destroy that, too.
    IF Widget_Info(self.tlb, /VALID_ID) THEN Widget_Control, self.tlb, /Destroy

END


;+
; This method adds a cgLegendItem object or array of objects to the plot. The
; legend objects are drawn on the plot after the orginal plot is drawn.
;
; :Params:
;    legendobject: in, optional, type=object
;        A cgLegendItem object, or an array of cgLegendItem objects that should be drawn
;        when the Draw method is called. The legend objects will be destroyed when this
;        object is destroyed.
;        
; :Keywords:
;     clear: in, optional, type=boolean, default=0
;        If this keyword is set, the overplot list is cleared before the new overplot objects
;        are added. Otherwise, the overplot object or objects is added to the end of the list
;        already present.
;     draw: in, optional, type=boolean, default=0
;        If this keyword is set, the object calls its Draw method after the overplot objects are added.
;     index: in, optional, type=integer, default=0
;         Used only if the `REPLACE` keyword is set. Specifies the replacement index.
;     replace: in, optional, type=boolean, default=0
;        If this keyword is set, the new object replaces a current object, in the object
;        array at the `INDEX` location.
;-
PRO cgZPlot::AddLegends, legendObject, $
   CLEAR=clear, $
   DRAW=draw, $
   INDEX=index, $
   REPLACE=replace

    Compile_Opt idl2
    
   ; Standard error handling.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      void = cgErrorMsg()
      RETURN
   ENDIF
  
   ; Clear all current legends, if needed.
   IF Keyword_Set(clear) THEN BEGIN
   
        ; Get rid of the overplot objects, if any.
        IF Ptr_Valid(self.legends) THEN BEGIN
           FOR j=0,N_Elements(*self.legends)-1 DO BEGIN
             Obj_Destroy, (*self.legends)[j]
           ENDFOR
           Ptr_Free, self.legends
         ENDIF

   ENDIF
   
   ; Are we replacing an object in the current list?
   ; If so, do it and return.
   IF Keyword_Set(replace) THEN BEGIN
   
       IF N_Elements(index) EQ 0 THEN index = 0
       Obj_Destroy, (*self.legends)[index]
       (*self.legends)[index] = legendObject
        RETURN
        
   ENDIF
   
   ; If nothing to add, return.
   IF N_Elements(legendObject) EQ 0 THEN BEGIN
       IF Ptr_Valid(self.legends) EQ 0 THEN self.legends = Ptr_New(/ALLOCATE_HEAP)
       RETURN
   ENDIF
   
   ; Otherwise, add the legend objects.
   IF Ptr_Valid(self.legends) THEN BEGIN
       *self.legends = [*self.legends, legendObject]
   ENDIF ELSE BEGIN
       self.legends = Ptr_New(legendObject)
   ENDELSE
   help, *self.legends
   ; Draw the object?
   IF Keyword_Set(draw) THEN self -> Draw
   
END

;+
; This method adds a cgOverplot object or array of objects to the plot. The
; overplot objects are drawn on the plot after the orginal plot is drawn.
;
; :Params:
;    oplotobject: in, optional, type=object
;        A cgOverPlot object, or an array of cgOverplot objects that should be overplot
;        when the Draw method is called. The overplot objects will be destroyed when this
;        object is destroyed.
;        
; :Keywords:
;     clear: in, optional, type=boolean, default=0
;        If this keyword is set, the overplot list is cleared before the new overplot objects
;        are added. Otherwise, the overplot object or objects is added to the end of the list
;        already present.
;     draw: in, optional, type=boolean, default=0
;        If this keyword is set, the object calls its Draw method after the overplot objects are added.
;     index: in, optional, type=integer, default=0
;         Used only if the `REPLACE` keyword is set. Specifies the replacement index.
;     replace: in, optional, type=boolean, default=0
;        If this keyword is set, the new object replaces a current object, in the object
;        array at the `INDEX` location.
;-
PRO cgZPlot::AddOverplots, oplotObject, $
   CLEAR=clear, $
   DRAW=draw, $
   INDEX=index, $
   REPLACE=replace

    Compile_Opt idl2
    
   ; Standard error handling.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      void = cgErrorMsg()
      RETURN
   ENDIF
  
   ; Are we replacing an object in the current list?
   ; If so, do it and return.
   IF Keyword_Set(replace) THEN BEGIN
   
       IF N_Elements(index) EQ 0 THEN index = 0
       Obj_Destroy, (*self.oplots)[index]
       (*self.oplots)[index] = oplotObject
        RETURN
        
   ENDIF
   
   ; Clear all current overplots, if needed.
   IF Keyword_Set(clear) THEN BEGIN
   
        ; Get rid of the overplot objects, if any.
        IF Ptr_Valid(self.oplots) THEN BEGIN
           FOR j=0,N_Elements(*self.oplots)-1 DO BEGIN
             Obj_Destroy, (*self.oplots)[j]
           ENDFOR
           Ptr_Free, self.oplots
         ENDIF

   ENDIF
   
   ; If nothing to add, return.
   IF N_Elements(oplotObject) EQ 0 THEN BEGIN
       IF Ptr_Valid(self.oplots) EQ 0 THEN self.oplots = Ptr_New(/ALLOCATE_HEAP)
       RETURN
   ENDIF
   
   ; Otherwise, add the overplot objects.
   IF Ptr_Valid(self.oplots) THEN BEGIN
       *self.oplots = [*self.oplots, oplotObject]
   ENDIF ELSE BEGIN
       self.oplots = Ptr_New(oplotObject)
   ENDELSE
   
   ; Draw the object?
   IF Keyword_Set(draw) THEN self -> Draw
   
END



;+
; This event handler will adjust the data Y range of the line plot to include all
; of the data in the current data X range, even if that data is currently not being
; displayed. 
; 
; :Params:
; 
;    event: in, optional, type=structure
;        The event structure passed by the window manager. Not used in this event handler.
;-
PRO cgZPlot::AdjustRange, event

    Compile_Opt idl2
    
   ; Standard error handling.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      void = cgErrorMsg()
      RETURN
   ENDIF
      
   ; Get the current X range of the data display.
   xrange = *self.xrange
   
   ; Locate the end points of that data range in the original data vector.
   endpoints = 0 > Value_Locate(*self.indep, xrange) < (N_Elements(*self.indep)-1)
   
   ; Find the corresponding points in the dependent data vector, and calculate the
   ; Y range from those.
   points = (*self.dep)[endpoints[0]:endpoints[1]]
   minrange = Min(points, Max=maxrange)
   fudge = Abs(maxrange-minrange) * 0.05
   *self.yrange = [minrange-fudge, maxrange+fudge]
   
   ; Redraw the plot.
   self -> Draw
END


;+
; Button down events are processed in this event handler method. Depending
; on which button is pressed and where the button is pressed in the graphics
; window, this method will either handle or dispatch the event to the appropriate 
; event handler.
; 
; :Params:
; 
;    event: in, required, type=structure
;        The event structure passed by the window manager.
;-
PRO cgZPlot::Button_Events, event

    Compile_Opt idl2
    
   ; Standard error handling.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      void = cgErrorMsg()
      RETURN
   ENDIF
   
   ; Only interested in button down events.
   IF event.type NE 0 THEN RETURN
   
   ; Left or right button determines the "mode" of the draw widget.
   ; Left button (1) is zoom, right button (4) is pan. Everything else
   ; is ignored.
   CASE event.press OF
      1: mode = 0
      4: mode = 1
      ELSE: RETURN
   ENDCASE
   
   ; Where the click is determines what we do next. If the click is below bottom the X axis,
   ; we will zoom the X axis out. If the click is above the top X axis, we will zoom the X axis 
   ; in. If the click is to the left of the Y axis we will zoom the Y axis out, if the click is
   ; to the right of the Y axis we will zoom the Y axis in.
   IF ~self -> InsidePlot(event.x, event.y) THEN BEGIN
      
      ; Need to do exact axis scaling to do smooth panning and zooming.
      IF N_Elements(*self.xstyle) NE 0 THEN *self.xstyle = *self.xstyle && 1 ELSE *self.xstyle = 1
      IF N_Elements(*self.ystyle) NE 0 THEN *self.ystyle = *self.ystyle && 1 ELSE *self.ystyle = 1
      
      ; Convert the click to normalized coordinates.
      !X = self.bangX
      !Y = self.bangY
      !P = self.bangP
      WSet, self.wid
      xy = Convert_Coord(event.x, event.y, /Device, /To_Normal)
      xn = xy[0]
      yn = xy[1]
      zf = self.zoomfactor
      
      ; Click is below bottom X axis.
      IF (xn GT !X.Window[0]) && (xn LT !X.Window[1]) && (yn LT !Y.Window[0]) THEN BEGIN
          distance = Abs((*self.xrange)[1] - (*self.xrange)[0]) * zf
          (*self.xrange)[0] = ((*self.xrange)[0] - distance) > self.orig_xrange[0]
          (*self.xrange)[1] = ((*self.xrange)[1] + distance) < self.orig_xrange[1]
      ENDIF
      
      ; Click is above the top X axis.
      IF (xn GT !X.Window[0]) && (xn LT !X.Window[1]) && (yn GT !Y.Window[1]) THEN BEGIN
          distance = Abs((*self.xrange)[1] - (*self.xrange)[0]) * zf
          (*self.xrange)[0] = ((*self.xrange)[0] + distance) > self.orig_xrange[0]
          (*self.xrange)[1] = ((*self.xrange)[1] - distance) < self.orig_xrange[1]
          IF (*self.xrange)[0] GE (*self.xrange)[1] THEN (*self.xrange)[0] = (*self.xrange)[1] - distance
      ENDIF

      ; Click is to left of left Y axis.
      IF (yn GT !Y.Window[0]) && (yn LT !Y.Window[1]) && (xn LT !X.Window[0]) THEN BEGIN
          distance = Abs((*self.yrange)[1] - (*self.yrange)[0]) * zf
          (*self.yrange)[0] = ((*self.yrange)[0] - distance) > self.orig_yrange[0]
          (*self.yrange)[1] = ((*self.yrange)[1] + distance) < self.orig_yrange[1]
      ENDIF
      
      ; Click is to right of right Y axis.
      IF (yn GT !Y.Window[0]) && (yn LT !Y.Window[1]) && (xn GT !X.Window[1]) THEN BEGIN
          distance = Abs((*self.yrange)[1] - (*self.yrange)[0]) * zf
          (*self.yrange)[0] = ((*self.yrange)[0] + distance) > self.orig_yrange[0]
          (*self.yrange)[1] = ((*self.yrange)[1] - distance) < self.orig_yrange[1]
          IF (*self.yrange)[0] GE (*self.yrange)[1] THEN (*self.yrange)[0] = (*self.yrange)[1] - distance
      ENDIF
      
      self -> Draw
      RETURN
      
   ENDIF
   
   ; Store the current click location.
   self.x0 = event.x
   self.y0 = event.y
   
   ; Send the event to the proper event handler, depending on the mode.
   Widget_Control, self.drawID, /Clear_Events
   CASE mode OF
       0: BEGIN ; Zooming
          Widget_Control, self.drawID, Set_UValue={method:'Zoom_Events', object:self}
          END
       1: BEGIN ; Panning
          Widget_Control, self.drawID, Set_UValue={method:'Pan_Events', object:self}
          self. drag = 1
          END
   ENDCASE
   
   ; Turn motion events on for this widget.
   Widget_Control, self.drawID, DRAW_MOTION_EVENTS=1
     
END

;+
; This method copies the contents of the pixmap into the display window.
;-
PRO cgZPlot::CopyPixmap

    Compile_Opt idl2
    
    WSet, self.wid
    Device, Copy=[0, 0, self.xsize, self.ysize, 0, 0, self.pixmapID]

END   

;+
; This method destroys the object and the GUI, if it still exists.
;-
PRO cgZplot::Destroy
   Obj_Destroy, self
END

;+
; This is the standard drawing method for the object. For smooth operation,
; the graphics are pixmap buffered. The plot is drawn into the pixmap, then
; copied to the draw widget window.
;-
PRO cgZPlot::Draw

    Compile_Opt idl2
    
    ; Standard error handling.
    Catch, theError
    IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      void = cgErrorMsg()
      RETURN
    ENDIF
   
    WSet, self.pixmapID
    cgErase
    
   
    ; Draw the plot itself.
    self -> DrawPlot
           
    ; Save the plot system variables.
    self.bangX = !X
    self.bangY = !Y
    self.bangP = !P
   
    ; Make sure we are drawing into the right window.
    WSet, self.wid
    Device, Copy=[0, 0, self.xsize, self.ysize, 0, 0, self.pixmapID]
    
    ; We need to save the current position of the plot in the window,
    ; so we can determine if clicks are inside or outside this position.
    self.current_position = [!X.Window[0], !Y.Window[0], !X.Window[1], !Y.Window[1]]
    
    ; Save the current configuration on the undoList.
    IF self.drag EQ 0 THEN self -> UndoList
    
END

;+
; This method simply gets the keywords it needs and draws the line plot.
; It was created primarily so the OUTPUT keyword could be used with the
; cgPlot command, since all the file output infrastruction has been built
; into that command.
; 
; :Keywords:
;    
;     output: in, optional, type=string
;         The name of the output file. File type is determined by the file extension.
;-
PRO cgZPlot::DrawPlot, OUTPUT=output

    Compile_Opt idl2
    
    ; Standard error handling.
    Catch, theError
    IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      void = cgErrorMsg()
      RETURN
    ENDIF

   ; Get the graphics keywords.
   self -> cgGraphicsKeywords::GetProperty, $
        AXISCOLOR=axiscolor, $
        BACKGROUND=background, $
        CHARSIZE=charsize, $
        CHARTHICK=charthick, $
        CLIP=clip, $
        COLOR=color, $
        DATA=data, $
        DEVICE=device, $
        NORMAL=normal, $
        FONT=font, $
        NOCLIP=noclip, $
        NODATA=nodata, $
        NOERASE=noerase, $
        POSITION=position, $
        PSYM=psym, $
        SUBTITLE=subtitle, $
        SYMSIZE=symsize, $
        T3D=t3d, $
        THICK=thick, $
        TICKLEN=ticklen, $
        TITLE=title, $
        
        XCHARSIZE=xcharsize, $
        XGRIDSTYLE=xgridstyle, $
        XMARGIN=xmargin, $
        XMINOR=xminor, $
        XRANGE=xrange, $
        XSTYLE=xstyle, $
        XTHICK=xthick, $
        XTICK_GET=xtick_get, $
        XTICKFORMAT=xtickformat, $
        XTICKINTERVAL=xtickinterval, $
        XTICKLAYOUT=xticklayout, $
        XTICKLEN=xticklen, $
        XTICKNAME=xtickname, $
        XTICKS=xticks, $
        XTICKUNITS=xtickunits, $
        XTICKV=xtickv, $
        XTITLE=xtitle, $
        
        YCHARSIZE=ycharsize, $
        YGRIDSTYLE=ygridstyle, $
        YMARGIN=ymargin, $
        YMINOR=yminor, $
        YRANGE=yrange, $
        YSTYLE=ystyle, $
        YTHICK=ythick, $
        YTICK_GET=ytick_get, $
        YTICKFORMAT=ytickformat, $
        YTICKINTERVAL=ytickinterval, $
        YTICKLAYOUT=yticklayout, $
        YTICKLEN=yticklen, $
        YTICKNAME=ytickname, $
        YTICKS=yticks, $
        YTICKUNITS=ytickunits, $
        YTICKV=ytickv, $
        YTITLE=ytitle, $
       
        ZCHARSIZE=zcharsize, $
        ZGRIDSTYLE=zgridstyle, $
        ZMARGIN=zmargin, $
        ZMINOR=zminor, $
        ZRANGE=zrange, $
        ZSTYLE=zstyle, $
        ZTHICK=zthick, $
        ZTICK_GET=ztick_get, $
        ZTICKFORMAT=ztickformat, $
        ZTICKINTERVAL=ztickinterval, $
        ZTICKLAYOUT=zticklayout, $
        ZTICKLEN=zticklen, $
        ZTICKNAME=ztickname, $
        ZTICKS=zticks, $
        ZTICKUNITS=ztickunits, $
        ZTICKV=ztickv, $
        ZTITLE=ztitle, $
        ZVALUE=zvalue
        
   ; Draw the plot.
   cgPlot, *self.indep, *self.dep, $
        OUTPUT=output, $
        ASPECT=*self.aspect, $
        LABEL=self.label, $
        MAX_VALUE=*self.max_value, $
        MIN_VALUE=*self.min_value, $
        XLOG=*self.xlog, $
        YLOG=*self.ylog, $
        YNOZERO=*self.ynozero, $
        AXISCOLOR=axiscolor, $
        BACKGROUND=background, $
        CHARSIZE=charsize, $
        CHARTHICK=charthick, $
        CLIP=clip, $
        COLOR=color, $
        DATA=data, $
        DEVICE=device, $
        NORMAL=normal, $
        FONT=font, $
        LEGENDS=*self.legends, $
        NOCLIP=noclip, $
        NODATA=nodata, $
        NOERASE=noerase, $
        OPLOTS=*self.oplots, $
        POSITION=position, $
        PSYM=psym, $
        SUBTITLE=subtitle, $
        SYMSIZE=symsize, $
        T3D=t3d, $
        THICK=thick, $
        TICKLEN=ticklen, $
        TITLE=title, $
        XCHARSIZE=xcharsize, $
        XGRIDSTYLE=xgridstyle, $
        XMARGIN=xmargin, $
        XMINOR=xminor, $
        XRANGE=xrange, $
        XSTYLE=xstyle, $
        XTHICK=xthick, $
        XTICK_GET=xtick_get, $
        XTICKFORMAT=xtickformat, $
        XTICKINTERVAL=xtickinterval, $
        XTICKLAYOUT=xticklayout, $
        XTICKLEN=xticklen, $
        XTICKNAME=xtickname, $
        XTICKS=xticks, $
        XTICKUNITS=xtickunits, $
        XTICKV=xtickv, $
        XTITLE=xtitle, $
        YCHARSIZE=ycharsize, $
        YGRIDSTYLE=ygridstyle, $
        YMARGIN=ymargin, $
        YMINOR=yminor, $
        YRANGE=yrange, $
        YSTYLE=ystyle, $
        YTHICK=ythick, $
        YTICK_GET=ytick_get, $
        YTICKFORMAT=ytickformat, $
        YTICKINTERVAL=ytickinterval, $
        YTICKLAYOUT=yticklayout, $
        YTICKLEN=yticklen, $
        YTICKNAME=ytickname, $
        YTICKS=yticks, $
        YTICKUNITS=ytickunits, $
        YTICKV=ytickv, $
        YTITLE=ytitle, $
        ZCHARSIZE=zcharsize, $
        ZGRIDSTYLE=zgridstyle, $
        ZMARGIN=zmargin, $
        ZMINOR=zminor, $
        ZRANGE=zrange, $
        ZSTYLE=zstyle, $
        ZTHICK=zthick, $
        ZTICK_GET=ztick_get, $
        ZTICKFORMAT=ztickformat, $
        ZTICKINTERVAL=ztickinterval, $
        ZTICKLAYOUT=zticklayout, $
        ZTICKLEN=zticklen, $
        ZTICKNAME=ztickname, $
        ZTICKS=zticks, $
        ZTICKUNITS=ztickunits, $
        ZTICKV=ztickv, $
        ZTITLE=ztitle, $
        ZVALUE=zvalue
        
END


;+
; This method simply erases the display.
;-
PRO cgZPlot::Erase

    Compile_Opt idl2
    
   ; Standard error handling.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      void = cgErrorMsg()
      RETURN
   ENDIF
 
    WSet, self.pixmapID
    cgErase
    self -> CopyPixmap
    
END



;+
; This event handler method allows the plot in the graphics window to be output
; to a file.
; 
; :Params:
; 
;    event: in, required, type=structure
;        The event structure passed by the window manager.
;-
PRO cgZPlot::FileOutput, event

    Compile_Opt idl2
    
   ; Standard error handling.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      void = cgErrorMsg()
      RETURN
   ENDIF
   
   ; What kind of output does the user want?
   Widget_Control, event.id, Get_Value=fileType
   CASE StrUpCase(fileType) OF
      'BMP FILE':  filename = 'cgzplot.bmp'
      'GIF FILE':  filename = 'cgzplot.gif'
      'EPS FILE':  filename = 'cgzplot.eps'
      'JPEG FILE': filename = 'cgzplot.jpg'
      'PDF FILE':  filename = 'cgzplot.pdf'
      'PNG FILE':  filename = 'cgzplot.png'
      'PS FILE':   filename = 'cgzplot.ps'
      'TIFF FILE': filename = 'cgzplot.tif'
   ENDCASE
   
   IF self.saveDir NE "" THEN thisDir = self.saveDir ELSE CD, CURRENT=thisDir
   
   filename = cgPickfile(PATH=thisDir, File=filename, GET_PATH=saveDir, $
       Title='Save Plot As...', /Write)
   IF filename EQ "" THEN RETURN
   
   self.saveDir = saveDir
   self -> DrawPlot, OUTPUT=filename
   
   
END

;+
; The properties of the object (keywords) are retrieved with this method.
; 
; :Keywords:
;     aspect: out, optional, type=float
;        A value that represents the aspect ratio (ysize/xsize) of the resulting plot. 
;     label: out, optional, type=string
;         The label that is used for the zoom plot.
;     legends: out, optional, type=object
;         The current legend objects, if there are any. If not, a null object.
;     max_value: out, optional, type=float
;         The maximum value to plot. 
;     min_value: out, optional, type=float
;         The minimum value to plot. 
;     oplots: out, optional, type=object
;         The current overplot objects, if there are any. If not, a null object.
;     undolist: out, optional, type=objref
;         The LinkedList object that maintains the undo list.
;     xlog: out, optional, type=boolean
;         Set if a logarithmic X axis is used in the plot.
;     ylog: out, optional, type=boolean
;         Set if a logarithmic Y axis is used in the plot.
;     ynozero: out, optional, type=boolean
;         Set if this property of the plot is set.
;     zoomfactor: out, optional, type=float
;         Set to the current zoom factor.
;     _ref_extra: out, optional, type=any
;        Any keyword appropriate for the IDL Plot or Coyote Graphic cgPlot command is 
;        allowed in the program.
;-
PRO cgZPlot::GetProperty, $
        DATA_X=indep, $
        DATA_Y=dep, $
        ASPECT=aspect, $
        LABEL=label, $
        LEGENDS=legends, $
        MAX_VALUE=max_value, $
        MIN_VALUE=min_value, $
        OPLOTS=oplots, $
        UNDOLIST=undolist, $
        XLOG=xlog, $
        YLOG=ylog, $
        YNOZERO=ynozero, $
        ZOOMFACTOR=zoomfactor, $
        _REF_EXTRA=extra
        
    Compile_Opt idl2
    
   ; Standard error handling.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      void = cgErrorMsg()
      RETURN
   ENDIF
   
   
    IF Arg_Present(indep) NE 0 THEN IF N_Elements(*self.indep) NE 0 THEN indep = *self.indep
    IF Arg_Present(dep) NE 0 THEN IF N_Elements(*self.dep) NE 0 THEN dep = *self.dep
    IF Arg_Present(aspect) NE 0 THEN IF N_Elements(*self.aspect) NE 0 THEN aspect = *self.aspect
    IF Arg_Present(label) NE 0 THEN IF N_Elements(self.label) NE 0 THEN label = self.label
    IF Arg_Present(legends) NE 0 THEN IF Ptr_Valid(self.legends) $
        THEN legends = *self.legends $
        ELSE legends = Obj_New()
    IF Arg_Present(max_value) NE 0 THEN IF N_Elements(*self.max_value) NE 0 THEN max_value = *self.max_value
    IF Arg_Present(min_value) NE 0 THEN IF N_Elements(*self.min_value) NE 0 THEN min_value = *self.min_value
    IF Arg_Present(oplots) NE 0 THEN IF Ptr_Valid(self.oplots) $
        THEN oplots = *self.oplots $
        ELSE oplots = Obj_New()
    IF Arg_Present(xlog) NE 0 THEN IF N_Elements(*self.xlog) NE 0 THEN xlog = *self.xlog
    IF Arg_Present(ylog) NE 0 THEN IF N_Elements(*self.ylog) NE 0 THEN ylog = *self.ylog
    IF Arg_Present(ynozero) NE 0 THEN IF N_Elements(*self.ynozero) NE 0 THEN ynozero = *self.ynozero
    IF Arg_Present(zoomfactor) NE 0 THEN zoomfactor = self.zoomfactor
    
    ; Get superclass properties.
    IF N_Elements(extra) NE 0 THEN self -> cgGraphicsKeywords::GetProperty, _STRICT_EXTRA=extra
END

;+
; This  method  simply determines if a button click is inside (returns 1)
; or outside (returns 0) the plot boundaries, determined by the plot axes.
; 
; :Params:
;    x: in, required, type=int
;        The X location of the button click in device coordinates.
;    y: in, required, type=int
;        The Y location of the button click in device coordinates.
;-
FUNCTION cgZPlot::InsidePlot, x, y

    Compile_Opt idl2
    
    On_Error, 2
    
    ; Convert device coordinate location to normalized coordinate location.
    ; Make sure you have the right system variables and window open.
    bangx = !X
    bangy = !Y
    bangp = !P
    !X = self.bangx
    !Y = self.bangy
    !P = self.bangp
    WSet, self.pixmapID
    
    ; Do the conversion.
    xy = Convert_Coord(x, y, /Device, /To_Normal)
    xpt = xy[0]
    ypt = xy[1]
    
    ; Restore the system variables.
    !X = bangx
    !Y = bangy
    !P = bangp
    
    ; Is this within the plot boundaries?
    p = self.current_position
    IF (xpt LT p[0]) || (xpt gt p[2]) || (ypt LT p[1]) || (ypt GT p[3]) THEN RETURN, 0 ELSE RETURN, 1
    
END


;+
; The purpose of this method is to draw the initial line plot in the draw widget.
; 
;-
PRO cgZPlot::Notify_Realize

   Compile_Opt idl2
   
   ; Standard error handling.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      void = cgErrorMsg()
      RETURN
   ENDIF

    Widget_Control, self.drawID, Get_Value=wid
    self.wid = wid
    
     ; Draw the initial plot.
    self -> Draw
    
END


;+
; This event handler method responds to panning events until it gets a button UP event.
; 
; :Params:
; 
;    event: in, required, type=structure
;        The event structure passed by the window manager.
;-
PRO cgZPlot::Pan_Events, event


   Compile_Opt idl2
   
   ; Standard error handling.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      void = cgErrorMsg()
      RETURN
   ENDIF
   
    ; Is this a button UP event? If it is, then we are done here.
    IF event.type EQ 1 THEN BEGIN
    
      ; Turn motion events off for this widget and clear all subsequent events.
      ; Restore the original event handler.
      Widget_Control, self.drawID, Draw_Motion_Events=0
      Widget_Control, self.drawID, /Clear_Events
      Widget_Control, self.drawID, Set_UValue={method:'Button_Events', object:self}
      self.drag = 0
    ENDIF
    
    ; Determine the end points of the pan and draw the plot. We are panning only in
    ; the X direction. The Y axes will reflect ALL the data points in the X range.
    ; Deterime the distance traveled in the XRange since you were last here.
    xpts = [self.x0, event.x]
    ypts = [self.y0, event.y]
    
    ; Locate these points in the data coordinate space.
    !X = self.bangX
    !Y = self.bangY
    !P = self.bangP
    WSet, self.wid
    xd = Convert_Coord(xpts, ypts, /Device, /To_Data)
    xdistance = (xd[0,0] - xd[0,1])
    ydistance = (xd[1,0] - xd[1,1])
    IF *self.xlog THEN BEGIN
        *self.xrange = (*self.xrange + xdistance) > 1e-6
    ENDIF ELSE BEGIN
        *self.xrange = (*self.xrange + xdistance)
    ENDELSE
    IF *self.ylog THEN BEGIN
        *self.yrange = (*self.yrange + ydistance) > 1e-6
    ENDIF ELSE BEGIN
        *self.yrange = (*self.yrange + ydistance)
    ENDELSE
    
    ; Update the static pan location.
    self.x0 = event.x
    self.y0 = event.y
    
    ; Draw the plot.
    self -> Draw
    
END


;+
; This method resizes the draw widget.
; 
; :Params:
; 
;    xsize: in, required, type=integer
;        The requested X size of the draw widget.
;    ysize: in, required, type=integer
;        The requested Y size of the draw widget.
;        
; :Keywords:
; 
;     draw: in, optional, type=boolean, default=0
;        If this keyword is set, the DRAW method is called after the widget is resized.
;-
PRO cgZPlot::ResizeDrawWidget, xsize, ysize, DRAW=draw

   Compile_Opt idl2
   
   ; Standard error handling.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      void = cgErrorMsg()
      RETURN
   ENDIF
   
   ; Delete and recreate the pixmap at the right size. 
   WDelete, self.pixmapID
   Window, /Pixmap, /Free, XSize=xsize, YSize=ysize
   self.pixmapID = !D.Window
   
   ; Make the draw widget the right size.
   Widget_Control, self.drawID, Draw_XSize=xsize, Draw_YSize=ysize
   self.xsize = xsize
   self.ysize = ysize

    ; Draw the plot?
    IF Keyword_Set(draw) THEN self -> Draw
    
END


;+
; This method allow plot keywords to be set to appropriate values.
; 
; :Keywords:
;
;     aspect: in, optional, type=float, default=none
;        Set this keyword to a floating point ratio that represents the aspect ratio 
;        (ysize/xsize) of the resulting plot. The plot position may change as a result
;        of setting this keyword. Note that `Aspect` cannot be used when plotting with
;        !P.MULTI.
;     dep: in, optional, type=any
;         The dependent data to plot.
;     draw: in, optional, type=boolean, default=0
;         Set this keyword if you would like to immediately draw the plot after properties are set.
;     indep: in, optional, type=any
;         The independent data to plot.
;     label: in, optional, type=string
;         A label is similar to a plot title, but it is aligned to the left edge
;         of the plot and is written in hardware fonts. Use of the label keyword
;         will suppress the plot title.
;     legends: in, optional, type=object
;         A single cgLegendItem object, or an array of cgLegendItem objects that will be
;         drawn on the plot as a legend.
;     max_value: in, optional, type=float
;        Set this keyword to the maximum value to plot. Any values greater than this 
;        value are treated as missing.
;     min_value: in, optional, type=float
;        Set this keyword to the minimu value to plot. Any values smaller than this 
;        value are treated as missing.
;     oplots: in, optional, type=object
;         A single cgOverPlot object, or an array of cgOverPlot objects that will be
;         overplot on the axes set up by the original data.
;     xlog: in, optional, type=boolean, default=0
;         Set this keyword to use a logarithmic X axis
;     ylog: in, optional, type=boolean, default=0
;         Set this keyword to use a logarithmic Y axis
;     ynozero: in, optional, type=boolean, default=0
;         Set this keyword to use allow the Y axis to start at a value other than zero.
;     zoomfactor: in, optional, type=float
;         Set this keyword to a number between 0.01 and 0.25. This affects the amount
;         of zooming when the X axis and Y axis are zoomed with the LEFT mouse button.
;         The default value is 0.05 or five percent of the current axis range on each
;         end of the axis, resulting in a 10 percent change in the axis length.
;     _extra: in, optional, type=any
;        Any keyword appropriate for the IDL Plot or Coyote Graphic cgPlot command is 
;        allowed in the program.
;-
PRO cgZPlot::SetProperty, $
        ASPECT=aspect, $
        DEP=dep, $
        DRAW=draw, $
        INDEP=indep, $
        LABEL=label, $
        LEGENDS=legends, $
        MAX_VALUE=max_value, $
        MIN_VALUE=min_value, $
        OPLOTS=oplots, $
        XLOG=xlog, $
        YLOG=ylog, $
        YNOZERO=ynozero, $
        ZOOMFACTOR=zoomfactor, $
        _EXTRA=extra
        
   Compile_Opt idl2
   
   ; Standard error handling.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      void = cgErrorMsg()
      RETURN
   ENDIF
   
    
    IF N_Elements(indep) NE 0 THEN *self.indep = indep
    IF N_Elements(dep) NE 0 THEN *self.dep = dep
    IF N_Elements(aspect) NE 0 THEN *self.aspect = aspect
    IF N_Elements(label) NE 0 THEN self.label = label
    IF N_Elements(legends) NE 0 THEN self -> AddLegends, legends, /Clear
    IF N_Elements(max_value) NE 0 THEN *self.max_value = max_value
    IF N_Elements(min_value) NE 0 THEN *self.min_value = min_value
    IF N_Elements(oplots) NE 0 THEN self -> AddOverplots, oplots, /Clear
    IF N_Elements(xlog) NE 0 THEN *self.xlog = Keyword_Set(xlog)
    IF N_Elements(ylog) NE 0 THEN *self.ylog = Keyword_Set(ylog)
    IF N_Elements(ynozero) NE 0 THEN *self.ynozero = Keyword_Set(ynozero)
    IF N_Elements(zoomfactor) NE 0 THEN self.zoomfactor = zoomfactor
    
    ; Superclass keywords.
    IF N_Elements(extra) NE 0 THEN self -> cgGraphicsKeywords::SetProperty, _STRICT_EXTRA=extra
    
    ; Need to draw the plot?
    IF Keyword_Set(draw) THEN self -> Draw
        
END

;+
; This event handler method destroys the widget program.
; 
; :Params:
;    event: in, required, type=structure
;        The event structure passed by the window manager.
;-
PRO cgZPlot::Quit, event

   Widget_Control, event.top, /Destroy

END


;+
; This method performs the REDO action and restores the plot to
; it's previous condition.
; 
; :Params:
;    event: in, optional, type=structure
;        The event structure passed by the window manager. Not used in this method.
;-
PRO cgZPlot::Redo, event

   Compile_Opt idl2
   
   ; Standard error handling.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      void = cgErrorMsg()
      RETURN
   ENDIF
   
   ; Get the list count. Return if there is nothing in the list
   listCnt = self.redoList -> Get_Count()
   IF listCnt EQ 0 THEN BEGIN
      void = Dialog_Message('Nothing to REDO')
      RETURN
   ENDIF
   
   ; Retrieve the last item on this list.
   item = self.redoList -> Get_Item()
   
   ; Remove the last item from the list.
   IF listCnt GT 1 THEN self.redoList -> Delete

   ; Update the range variables and draw the plot.
   IF listCnt GE 1 THEN BEGIN
     *self.xrange = item.xrange
     *self.yrange = item.yrange
     self -> Draw
   ENDIF
   
END


;
;+
; This event handler method resizes the graphics window.
; 
; :Params:
;    event: in, required, type=structure
;        The event structure passed by the window manager.
;-
PRO cgZPlot::TLB_Resize_Events, event

   Compile_Opt idl2
   
   ; Standard error handling.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      void = cgErrorMsg()
      RETURN
   ENDIF
   
   WDelete, self.pixmapID
   Window, /Pixmap, /Free, XSize=event.x, YSize=event.y
   self.pixmapID = !D.Window
   Widget_Control, self.drawID, Draw_XSize=event.x, Draw_YSize=event.y
   self.xsize = event.x
   self.ysize = event.y
   self -> Draw
   
END


;+
; This method performs the UNDO action and restores the plot to
; it's previous condition.
; 
; :Params:
;    event: in, optional, type=structure
;        The event structure passed by the window manager. Not used in this method.
;-
PRO cgZPlot::Undo, event

   Compile_Opt idl2
   
   ; Standard error handling.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      void = cgErrorMsg()
      RETURN
   ENDIF
   
   ; Get the list count. Return if there is nothing in the list
   listCnt = self.undoList -> Get_Count()
   IF listCnt EQ 0 THEN RETURN
   
   ; Retrieve the last item from the list, and add it to the REDO list.
   item = self.undoList -> Get_Item()
   self.redoList -> Add, item
   
   ; Remove the last item from the list.
   IF listCnt GT 1 THEN self.undoList -> Delete
   
   ; Get the last item on the list.
   item = self.undoList -> Get_Item()
   
   ; Now remove this item from the list, too, if it is not the last one
   IF listCnt GT 1 THEN self.undoList -> Delete
   
   ; Set the ranges and redraw the plot.
   IF listCnt GE 1 THEN BEGIN
     *self.xrange = item.xrange
     *self.yrange = item.yrange
     self -> Draw
   ENDIF
   
END


;+
; This method maintains the UNDO list. The list has a maximum undo capacity of 50.
;-
PRO cgZPlot::UndoList

   Compile_Opt idl2
   
   ; Standard error handling.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      void = cgErrorMsg()
      RETURN
   ENDIF
   
   ; Prepare the structure that will be stored on the list.
   IF N_Elements(*self.xrange) EQ 0 THEN *self.xrange = self.orig_xrange
   IF N_Elements(*self.yrange) EQ 0 THEN *self.yrange = self.orig_yrange
   undoStruct = {xrange:*self.xrange, yrange:*self.yrange}
   
   ; How many items are on the list already? If 50, delete the first
   ; item on the list.
   listCnt = self.undoList -> Get_Count()
   IF listCnt GE 50 THEN self.undoList -> Delete, 0
   
   ; Add the item to the list.
   self.undoList -> Add, undoStruct

END


;+
; This event handler method allows the user to create a rubber-band box for zooming
; into the line plot.
; 
; :Params:
;    event: in, required, type=structure
;        The event structure passed by the window manager.
;-
PRO cgZPlot::Zoom_Events, event

    ; Is this a button UP event? If it is, all the action is done here.
    ; If it is not, then it must be a MOTION event, and we simply draw
    ; and erase the zoom box.
    
   Compile_Opt idl2
   
   ; Standard error handling.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      void = cgErrorMsg()
      RETURN
   ENDIF
   
    ; What kind of event is this?
    CASE event.type OF
        1: BEGIN ; button UP event
        
           ; Turn motion events off for this widget and clear all subsequent events.
           ; Restore the original event handler.
           Widget_Control, self.drawID, Draw_Motion_Events=0
           Widget_Control, self.drawID, /Clear_Events
           Widget_Control, self.drawID, Set_UValue={method:'Button_Events', object:self}
           self -> CopyPixmap
           
           xtest = [self.x0, event.x]
           ytest = [self.y0, event.y]
           x = [Min(xtest), Max(xtest)]
           y = [Min(ytest), Max(ytest)]
           
           ; Make sure the up event is inside the plot.
           !X = self.bangX
           !Y = self.bangY
           !P = self.bangP
           xy = Convert_Coord(x, y, /Device, /To_Normal)
           p = self.current_position
           xn = p[0] > xy[0,*] < p[2]
           yn = p[1] > xy[1,*] < p[3]
           
           ; Convert these normalized coordinates to data coordinates.
           xy = Convert_Coord(xn, yn, /Normal, /To_Data)
           xd = Reform(xy[0,*])
           yd = Reform(xy[1,*])
           
           ; The range depends on whether you are using log axes or not.
           IF *self.xlog THEN BEGIN
              x = 10^!X.CRange[0] > xd < 10^!X.CRange[1]
           ENDIF ELSE BEGIN
              x = !X.CRange[0] > xd < !X.CRange[1]
           ENDELSE
           IF *self.ylog THEN BEGIN
              y = 10^!Y.CRange[0] > yd < 10^!Y.CRange[1]
           ENDIF ELSE BEGIN
              y = !Y.CRange[0] > yd < !Y.CRange[1]
           ENDELSE
           *self.xrange = x
           *self.yrange = y
            
           ; Draw the plot.
           self -> Draw
           END
           
        2: BEGIN ; motion event
        
           ; Erase whatever is currently on the display.
           self -> CopyPixmap
           
           ; Draw the new box.
           x = [self.x0, self.x0, event.x, event.x, self.x0]
           y = [self.y0, event.y, event.y, self.y0, self.y0]
           PlotS, x, y, Color=cgColor('NAVY'), Thick=2, /Device
           END
        ELSE: 
    ENDCASE

END


;+
; This is the main event handler for the program. All events come here
; to be distributed to the appropriate event handler method according
; to instructions packed into the UVALUE of any widget generating an
; event.
; 
; :Params:
;    event: in, required, type=structure
;        The event structure passed by the window manager.
;-
PRO cgZPlot_Events, event

   Compile_Opt idl2
   
   ; Standard error handling.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      void = cgErrorMsg()
      RETURN
   ENDIF
   
    Widget_Control, event.id, Get_UValue=instructions
    Call_Method, instructions.method, instructions.object, event

END

;+
; This is the cleanup routine for the widget. Its function is to destroy
; the underlying program object.
; 
; :Params:
;    tlb: in, required, type=int
;        The widget identifier of the top-level base widget that just died.
;-
PRO cgZPlot_Cleanup, tlb

   Widget_Control, tlb, Get_UValue=instr
   Obj_Destroy, instr.object
   
END

;+
; This is the realize notify routine for the widget. Its function call the
; Realize_Notify method to draw the initial plot in the display window.
; 
; :Params:
;    id: in, required, type=int
;        The widget identifier of the widget that has been realized.
;-
PRO cgZPlot_Notify_Realize, id

   Widget_Control, id, Get_UValue=instructions
   Call_Method, 'Notify_Realize', instructions.object
   
END
;+
; The object class definition.
; 
; :Params:
;    class: out, optional, type=struct
;        The class definition object. Often helpful for obtaining fields of the object structure.
;-
PRO cgZPlot__Define, class

   Compile_Opt idl2
   
   class = { cgZPLOT, $
             INHERITS cgGraphicsKeywords, $
             ASPECT: Ptr_New(), $
             MAX_VALUE: Ptr_New(), $
             MIN_VALUE: Ptr_New(), $
             NSUM: Ptr_New(), $
             XLOG: Ptr_New(), $
             YLOG: Ptr_New(), $
             YNOZERO: Ptr_New(), $
             
             orig_xrange: DblArr(2), $
             orig_yrange: DblArr(2), $
             current_position: DblArr(4), $
             zoomFactor: 0.0, $
             undoList: Obj_New(), $
             redoList: Obj_New(), $
             oplots: Ptr_New(), $      ; A pointer to a cgOverPlot object or array of cgOverPlot objects.
             legends: Ptr_New(), $     ; A pointer to a cgLegendItem object or array of cgLegendItem objects.
             label: "", $              ; The plot label. Suppress TITLE if present.
             savedir: "", $            ; The output directory where files are saved.
             drag: 0B, $               ; A flag that tells me if I am panning or not. Necessary for UNDO.
             
             indep: Ptr_New(), $
             dep: Ptr_New(), $
             
             tlb: 0L, $
             drawID: 0L, $
             pixmapID: 0L, $
             wid: 0L, $
             xsize: 0L, $
             ysize: 0L, $
             x0: 0L, $
             y0: 0L,$
             x1: 0L, $
             y1: 0L, $
             mode: 0B, $   0 is zoom plot, 1 is pan plot.
             bangx: !X, $
             bangy: !Y, $
             bangp: !P $
            }

END