;+
; NAME:
;       ZPLOTXY 
;
; PURPOSE:
;
;       Based on Fanning ZPLOTXY but it zooms in both x and y direction using 
;       a box - Ed Esfandiari.
;       The purpose of this program is to display a line plot in a resizeable
;       graphics window which can be zoomed by drawing an "arrow box" on top
;       of it. To return to the un-zoomed plot, click and release anywhere in the window.
;
; AUTHOR:
;
;   FANNING SOFTWARE CONSULTING
;   David Fanning, Ph.D.
;   1645 Sheely Drive
;   Fort Collins, CO 80526 USA
;   Phone: 970-221-0438
;   E-mail: davidf@dfanning.com
;   Coyote's Guide to IDL Programming: http://www.dfanning.com/
;
; CATEGORY:
;
;      Widgets.
;
; CALLING SEQUENCE:
;
;      ZPlotxy, x, y
;
; OPTIONAL INPUTS:
;
;      x: If only one positional parameter, this is assumed to be the
;         dependent data. If there are two positional parameters, this
;         is assumed to be the independent data in accordance with the
;         PLOT command.
;
;      y: The dependent data, if the X parameter is present.
;
; KEYWORD PARAMETERS:
;
;       Any valid PLOT keyword can be used with this program. In additon,
;       the following keywords are defined specifically.
;
;       GROUP_LEADER: This keyword is used to assign a group leader to this
;                 program. This program will be destroyed when the group
;                 leader is destroyed. Use this keyword if you are calling
;                 ZIMAGE from another widget program.
;
;       XLOG: Set this keyword if you wish the X axis to be logarthmic style.
;
;       YLOG: Set this keyword if you wish the Y axis to be logarthmic style.
;
;       ZOOM_XSIZE: The initial X size of the plot window. Default is 400 pixels.
;
;       ZOOM_YSIZE: The initial Y size of the plot window. Default is 400 pixels.
;
; COMMON BLOCKS:
;
;       None.
;
; SIDE EFFECTS:
;
;       Drawing colors are loaded into the color table.
;
; RESTRICTIONS:
;
;       Zooms only along the X axis.
;       Requires FSC_Color from the Coyote Library.
;
; PROCEDURE:
;
;       Click and drag the cursor to create an "arrow box". The plot
;       is zoomed into the X coordinates of the box, when released.
;       To restore unzoomed plot, click and release anywhere in the
;       window.
;
; EXAMPLE:
;
;        To display an plot you can zoom into, type:
;
;        ZPLOTXY
;
; MODIFICATION HISTORY:
;        Based on:
;        Written by: David Fanning, 15 February 2000.
;        Modified the original rubberband box to be an "arrow box". 1 April 2000. DWF.
;        Added the ability to produce log style plots. 20 November 2001. DWF.
;        Replaced all references to GETCOLOR with FSC_COLOR. 1 March 2006. DWF.
;
; July 2008 - Ed Esfandiari Added Y-direction zoom and draw box.
;
;-
;###########################################################################
;
; LICENSE
;
; This software is OSI Certified Open Source Software.
; OSI Certified is a certification mark of the Open Source Initiative.
;
; Copyright © 2000-2006 Fanning Software Consulting
;
; This software is provided "as-is", without any express or
; implied warranty. In no event will the authors be held liable
; for any damages arising from the use of this software.
;
; Permission is granted to anyone to use this software for any
; purpose, including commercial applications, and to alter it and
; redistribute it freely, subject to the following restrictions:
;
; 1. The origin of this software must not be misrepresented; you must
;    not claim you wrote the original software. If you use this software
;    in a product, an acknowledgment in the product documentation
;    would be appreciated, but is not required.
;
; 2. Altered source versions must be plainly marked as such, and must
;    not be misrepresented as being the original software.
;
; 3. This notice may not be removed or altered from any source distribution.
;
; For more information on Open Source Software, visit the Open Source
; web site: http://www.opensource.org.
;
;###########################################################################



PRO ZPLOTXY_CLEANUP, tlb

   ; The purpose of this module is to delete the pixmap window
   ; and perform other cleanup chores when the program ZPLOTXY is
   ; destroyed.

Widget_Control, tlb, Get_UValue=info
IF N_Elements(info) NE 0 THEN BEGIN
   WDelete, info.pixIndex
   Ptr_Free, info.extraKeywords
ENDIF
END ; of ZPLOTXY_CLEANUP **********************************************************



PRO ZPLOTXY_RESIZE, event

; This event handler reponds to TLB re-size events.

 Widget_Control, event.top, Get_UValue=info, /No_Copy

   ; Update window sizes.

info.zxsize = event.x
info.zysize = event.y

   ; Destroy old pixmap and create a new one.

WDelete, info.pixIndex
Window, XSize=event.x, YSize=event.y, /Free, /Pixmap
info.pixIndex = !D. Window

   ; Resize draw widget.

Widget_Control, info.drawID, Draw_XSize=(event.x > 200), Draw_YSize=(event.y > 150)

   ;  Set up colors for line plot.

backColor = FSC_Color('charcoal', !D.Table_Size-2)
dataColor = FSC_Color('yellow', !D.Table_Size-3)
axisColor = FSC_Color('green', !D.Table_Size-4)

   ; Draw the plot in both windows.

Plot, info.indep, info.dep, XRange=info.xrange, XStyle=info.xstyle, YRange=info.yrange, $
   YStyle=info.ystyle, Background=backColor, Color=axisColor, /NoData, _Extra=*info.extraKeywords
OPlot, info.indep, info.dep, Color=dataColor

WSet, info.drawIndex
Plot, info.indep, info.dep, XRange=info.xrange, XStyle=1, YRange=info.yrange, YStyle=1, $
   Background=backColor, Color=axisColor, /NoData, _Extra=*info.extraKeywords
OPlot, info.indep, info.dep, Color=dataColor

   ; Update system parameters.

info.x = !X
info.y = !Y
info.p = !P

Widget_Control, event.top, Set_UValue=info, /No_Copy


END ; of ZPLOTXY_RESIZE **********************************************************



PRO ZPLOTXY_PROCESS_EVENTS, event

   ; This event handler ONLY responds to button down events from the
   ; draw widget. If it gets a DOWN event, it does three things: (1) sets
   ; the static and dynamic corners of the arrow box, (2) changes the
   ; event handler for the draw widget to ZPLOTXY_DRAWBOX and turns on MOTION
   ; events, and (3) update the user's color table vectors.

possibleEventTypes = [ 'DOWN', 'UP', 'MOTION', 'SCROLL' ]
thisEvent = possibleEventTypes(event.type)
IF thisEvent NE 'DOWN' THEN RETURN

    ; Must be DOWN event to get here, so get info structure.

 Widget_Control, event.top, Get_UValue=info, /No_Copy

    ; Set the static corners of the arrow box to current
    ; cursor location.

 info.xs = event.x
 info.ys = event.y

    ; Change the event handler for the draw widget and turn MOTION
    ; events ON.

 Widget_Control, event.id, Event_Pro='ZPLOTXY_DRAWBOX', $
    Draw_Motion_Events=1

   ; Put the info structure back into its storage location.

Widget_Control, event.top, Set_UValue=info, /No_Copy
END ; of ZPLOTXY_PROCESS_EVENTS *****************************************************



PRO ZPLOTXY_DRAWBOX, event

   ; This event handler continuously draws and erases the arrow box until it
   ; receives an UP event from the draw widget. Then it turns draw widget motion
   ; events OFF and changes the event handler for the draw widget back to
   ; ZPLOTXY_PROCESS_EVENTS.

   ; Get the info structure out of the top-level base.

Widget_Control, event.top, Get_UValue=info, /No_Copy

   ;  Set up colors for line plot.

backColor = FSC_Color('charcoal', !D.Table_Size-2)
dataColor = FSC_Color('yellow', !D.Table_Size-3)
axisColor = FSC_Color('green', !D.Table_Size-4)
boxColor = FSC_Color('beige', !D.Table_Size-5)

   ; What type of an event is this?

possibleEventTypes = [ 'DOWN', 'UP', 'MOTION', 'SCROLL' ]
thisEvent = possibleEventTypes(event.type)

IF thisEvent EQ 'UP' THEN BEGIN

      ; If this is an UP event, you need to erase the zoombox, restore
      ; the user's color table, turn motion events OFF, set the
      ; draw widget's event handler back to ZPLOTXY_PROCESS_EVENTS, and
      ; draw the "zoomed" plot in both the draw widget and the pixmap.

      ; Erase the arrow box one final time by copying the plot from the pixmap.

   WSet, info.drawIndex
   Device, Copy = [0, 0, info.zxsize, info.zysize, 0, 0, info.pixIndex]

      ; Turn motion events off and redirect the events to ZPLOTXY_PROCESS_EVENTS.

    Widget_Control, event.id, Draw_Motion_Events=0, $
       Event_Pro='ZPLOTXY_PROCESS_EVENTS'

      ; Draw the "zoomed" plot. Start by getting the new limits to the plot
      ; (i.e., the LAST zoom box outline).

   x = [info.xs, event.x]
   y = [info.ys, event.y]

      ; Make sure the x values are ordered as [min, max].

   IF info.xs GT event.x THEN x = [event.x, info.xs]

      ; Make sure the y values are ordered as [min, max].

   IF info.ys GT event.y THEN y = [event.y, info.ys]

      ; Don't want exact style if we are drawing entire plot.

   IF info.xs EQ event.x THEN xstyle = 0 ELSE xstyle = 1
   info.xstyle = xstyle

   IF info.ys EQ event.y THEN ystyle = 0 ELSE ystyle = 1
   info.ystyle = ystyle

      ; Restore plot system variables.

   !X = info.x
   !Y = info.y
   !P = info.p

      ; Convert the x device coordinates to data coordinates.

   coords = Convert_Coord(x, y, /Device, /To_Data)

      ; Make sure the x coordinates are within the data boundaries of the plot.

   IF info.xlog THEN BEGIN
      x1 = 10^!X.CRange(0) > coords(0,0) < 10^!X.CRange(1)
      x2 = 10^!X.CRange(0) > coords(0,1) < 10^!X.CRange(1)
   ENDIF ELSE BEGIN
      x1 = !X.CRange(0) > coords(0,0) < !X.CRange(1)
      x2 = !X.CRange(0) > coords(0,1) < !X.CRange(1)
   ENDELSE
   info.xrange = [x1,x2]

      ; Make sure the y coordinates are within the data boundaries of the plot.

   IF info.ylog THEN BEGIN
      y1 = 10^!Y.CRange(0) > coords(1,0) < 10^!Y.CRange(1)
      y2 = 10^!Y.CRange(0) > coords(1,1) < 10^!Y.CRange(1)
   ENDIF ELSE BEGIN
      y1 = !Y.CRange(0) > coords(1,0) < !Y.CRange(1)
      y2 = !Y.CRange(0) > coords(1,1) < !Y.CRange(1)
   ENDELSE
   info.yrange = [y1,y2]

      ; Draw the "zoomed" plot in both the draw widget and the pixmap.

   yDataRange = [Min(info.dep),Max(info.dep)]
   Plot, info.indep, info.dep, XRange=[x1,x2], XStyle=xstyle, YRange=[y1,y2], $
      Background=backColor, Color=axisColor, /NoData, _Extra=*info.extraKeywords, $
      XLog=info.xlog, YLog=info.ylog
   OPlot, info.indep, info.dep, Color=dataColor

   WSet, info.pixIndex
   Plot, info.indep, info.dep, XRange=[x1,x2], XStyle=xstyle, YRange=[y1,y2], $
      Background=backColor, Color=axisColor, /NoData, _Extra=*info.extraKeywords, $
      XLog=info.xlog, YLog=info.ylog
   OPlot, info.indep, info.dep, Color=dataColor

      ; Update the plot system variables.

   info.x = !X
   info.y = !Y
   info.p = !P

      ; Put the info structure back into its storage location and then, out of here!

   Widget_Control, event.top, Set_UValue=info, /No_Copy
   RETURN
ENDIF ; thisEvent = UP


   ; Most of the action in this event handler occurs here while we are waiting
   ; for an UP event to occur. As long as we don't get it, keep erasing the
   ; old zoom box and drawing a new one.

   ; Erase the old zoom box.

WSet, info.drawIndex
Device, Copy = [0, 0, info.zxsize, info.zysize, 0, 0, info.pixIndex]

   ; Update the dynamic corner of the zoom box to the current cursor location.

 info.xd = event.x
 info.yd = event.y

   ; Restore plot system variables.

!X = info.x
!Y = info.y
!P = info.p

   ; Convert the x device coordinates to data coordinates.

x = [info.xs, event.x]
y = [info.ys, event.y]
coords = Convert_Coord(x, y, /Device, /To_Data)

   ; Make sure the x coordinates are within the data boundaries of the plot.

IF info.xlog THEN BEGIN
   x1 = 10^!X.CRange(0) > coords(0,0) < 10^!X.CRange(1)
   x2 = 10^!X.CRange(0) > coords(0,1) < 10^!X.CRange(1)
ENDIF ELSE BEGIN
   x1 = !X.CRange(0) > coords(0,0) < !X.CRange(1)
   x2 = !X.CRange(0) > coords(0,1) < !X.CRange(1)
ENDELSE

IF info.ylog THEN BEGIN
   y1 = 10^!Y.CRange(0) > coords(1,0) < 10^!Y.CRange(1)
   y2 = 10^!Y.CRange(0) > coords(1,1) < 10^!Y.CRange(1)
ENDIF ELSE BEGIN
   y1 = !Y.CRange(0) > coords(1,0) < !Y.CRange(1)
   y2 = !Y.CRange(0) > coords(1,1) < !Y.CRange(1)
ENDELSE

   ; Draw the arrow box.

;Arrow, x1, y1, x2, y1, /Data, Color=boxColor, /Solid, HSize=12
PlotS, [x1,x2,x2,x1,x1],[y1,y1,y2,y2,y1],/data, Color=boxColor
IF info.ylog THEN BEGIN
    PlotS, [x1, x1], [y1,y1], Color=boxColor
    PlotS, [x2, x2], [y2,y2], Color=boxColor
ENDIF ELSE BEGIN
    PlotS, [x1, x1], [y1,y1], Color=boxColor
    PlotS, [x2, x2], [y2,y2], Color=boxColor
ENDELSE

   ; Put the info structure back into its storage location.

Widget_Control, event.top, Set_UValue=info, /No_Copy
END ; of ZPLOTXY_DRAWBOX ******************************************************************



PRO ZPLOTXY, x, y, Zoom_XSize=zxsize, Zoom_YSize=zysize, $
   Group_Leader=group, XLog=xlog, YLog=ylog, _Extra=extra

   ; This procedure allows the user to "zoom" into the data plot
   ; by drawing an arrow box around the part of the data to zoom into.
   ; The arrow box will be drawn and erased by using a pixmap and
   ; the "Device Copy" technique.

   ; On an error condition, return to the main level of IDL.

On_Error, 1

   ; Was data passed into the procedure? If not, create some.

CASE N_Params() OF

   0: BEGIN
   dep = Findgen(101)
   dep = Sin(dep/5) / Exp(dep/50)
   indep = Findgen(101)
   ENDCASE

   1: BEGIN
   dep = x
   indep = Findgen(N_Elements(dep))
   ENDCASE

   2: BEGIN
   dep = y
   indep = x
   ENDCASE

ENDCASE

   ;  Set up colors for line plot.

backColor = FSC_Color('charcoal', !D.Table_Size-2)
dataColor = FSC_Color('yellow', !D.Table_Size-3)
axisColor = FSC_Color('green', !D.Table_Size-4)

   ; Check for keywords. Set defaults if necessary.

IF N_Elements(zxsize) EQ 0 THEN zxsize = 400
IF N_Elements(zysize) EQ 0 THEN zysize = 400
xlog = Keyword_Set(xlog)
ylog = Keyword_Set(ylog)

   ; Create a top-level base for this program. No resizing of this base.

tlb = Widget_Base(Title='Resizeable Zoomable FlexPlot Window (Drag to resize, Make box to zoom, Click to unzoom)', TLB_Size_Events=1)

   ; The only thing in the top-level base is a draw widget.
   ; The draw widget will have its own event handler.

drawID = Widget_Draw(tlb, XSize=zxsize, YSize=zysize, $
   Button_Events=1, Event_Pro='ZPLOTXY_PROCESS_EVENTS')

   ; Realize the program.

Widget_Control, tlb, /Realize

   ; Get the window index number of the draw widget.
   ; Make the draw widget the current graphics window
   ; and draw the plot of the data in it. Make the
   ; X data range exactly fit the data.

Widget_Control, drawID, Get_Value=drawIndex
WSet, drawIndex
xrange = [Min(indep), Max(indep)]
yrange=[Min(dep),Max(dep)]

Plot, indep, dep, XRange=xrange, YRange=yrange, XLog=xlog, YLog=ylog, $
   Background=backColor, Color=axisColor, /NoData, _Extra=extra
OPlot, indep, dep, Color=dataColor

  ; Create a pixmap window the same size as the draw widget window.
  ; Store its window index number in a local variable. Draw the same
  ; plot you just put in the draw widget in the pixmap window.

Window, /Free, XSize=zxsize, YSize=zysize, /Pixmap
pixIndex = !D.Window
Plot, indep, dep, XRange=xrange, YRange=yrange, XLog=xlog, YLog=ylog, $
   Background=backColor, Color=axisColor, /NoData, _Extra=extra
OPlot, indep, dep, Color=dataColor

IF N_Elements(extra) EQ 0 THEN extraKeywords = Ptr_New(/Allocate_Heap) ELSE extraKeywords = Ptr_New(extra)

   ; Create an info structure to hold information required by the program.

info = { $
   dep:dep, $                       ; The dependent data to be plotted.
   indep:indep, $                   ; The independent data to be plotted.
   zxsize:zxsize, $                 ; The X size of the draw widget.
   zysize:zysize, $                 ; The Y size of the draw widget.
   drawID:drawID, $                 ; The widget identifier of the draw widget.
   drawIndex:drawIndex, $           ; The draw window index number.
   pixIndex:pixIndex, $             ; The pixmap window index number.
   xrange:xrange, $                 ; The current X range of the plot.
   yrange:yrange, $                 ; The current Y range of the plot.
   xlog:xlog, $                     ; A flag for X logarithmic axis style.
   ylog:ylog, $                     ; A flag for Y logarithmic axis style.
   xstyle:0, $                      ; The setting for the XStyle keyword.
   ystyle:0, $                      ; The setting for the YStyle keyword.
   xs:0, $                          ; X static corner of the zoom box.
   ys:0, $                          ; Y static corner of the zoom box.
   xd:0, $                          ; X dynamic corner of the zoom box.
   yd:0, $                          ; Y dynamic corner of the zoom box.
   x:!X, $                          ; The !X system variable after plot.
   y:!Y, $                          ; The !Y system variable after plot.
   p:!P, $                          ; The !P system variable after plot.
   extraKeywords:extraKeywords }    ; Extra plot keywords pointer.

   ; Store the info structure in the user value of the top-level base.

Widget_Control, tlb, Set_UValue=info, /No_Copy

   ; Register this program and set up the event loop.

XManager, 'ZPLOTXY', tlb, Cleanup='ZPLOTXY_Cleanup', Group_Leader=group, /No_Block, $
   Event_Handler='ZPLOTXY_Resize'
END ; of ZPLOTXY ****************************************************************************

