GRASS Programmer's Manual  6.5.svn(2012)-r51648
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
wxplot/base.py
Go to the documentation of this file.
00001 """!
00002 @package wxplot.base
00003 
00004 @brief Base classes for iinteractive plotting using PyPlot
00005 
00006 Classes:
00007  - base::PlotIcons
00008  - base::BasePlotFrame
00009 
00010 (C) 2011 by the GRASS Development Team
00011 
00012 This program is free software under the GNU General Public License
00013 (>=v2). Read the file COPYING that comes with GRASS for details.
00014 
00015 @author Michael Barton, Arizona State University
00016 """
00017 
00018 import os
00019 import sys
00020 
00021 import wx
00022 import wx.lib.plot as plot
00023 
00024 from core.globalvar    import ETCICONDIR
00025 from core.settings     import UserSettings
00026 from wxplot.dialogs    import TextDialog, OptDialog
00027 from core.render       import Map
00028 from icons.icon        import MetaIcon
00029 from gui_core.toolbars import BaseIcons
00030 
00031 import grass.script as grass
00032 
00033 PlotIcons = {
00034     'draw'         : MetaIcon(img = 'show',
00035                               label = _('Draw/re-draw plot')),
00036     'transect'     : MetaIcon(img = 'layer-raster-profile',
00037                               label = _('Draw transect in map display window to profile')),
00038     'options'      : MetaIcon(img = 'settings',
00039                               label = _('Plot options')),
00040     'statistics'   : MetaIcon(img = 'check',
00041                               label = _('Plot statistics')),
00042     'save'         : MetaIcon(img = 'save',
00043                               label = _('Save profile data to CSV file')),
00044     'quit'         : BaseIcons['quit'].SetLabel(_('Quit plot tool')),
00045     }
00046 
00047 class BasePlotFrame(wx.Frame):
00048     """!Abstract PyPlot display frame class"""
00049     def __init__(self, parent = None, id = wx.ID_ANY, size = wx.Size(700, 400),
00050                  style = wx.DEFAULT_FRAME_STYLE, rasterList = [],  **kwargs):
00051 
00052         wx.Frame.__init__(self, parent, id, size = size, style = style, **kwargs)
00053         
00054         self.parent = parent            # MapFrame
00055         self.mapwin = self.parent.MapWindow
00056         self.Map    = Map()             # instance of render.Map to be associated with display
00057         self.rasterList = rasterList    #list of rasters to plot
00058         self.raster = {}    # dictionary of raster maps and their plotting parameters
00059         self.plottype = ''
00060         
00061         self.linestyledict = { 'solid' : wx.SOLID,
00062                             'dot' : wx.DOT,
00063                             'long-dash' : wx.LONG_DASH,
00064                             'short-dash' : wx.SHORT_DASH,
00065                             'dot-dash' : wx.DOT_DASH }
00066 
00067         self.ptfilldict = { 'transparent' : wx.TRANSPARENT,
00068                             'solid' : wx.SOLID }
00069 
00070         #
00071         # Icon
00072         #
00073         self.SetIcon(wx.Icon(os.path.join(ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
00074                 
00075         #
00076         # Add statusbar
00077         #
00078         self.statusbar = self.CreateStatusBar(number = 2, style = 0)
00079         self.statusbar.SetStatusWidths([-2, -1])
00080 
00081         #
00082         # Define canvas and settings
00083         #
00084         # 
00085         self.client = plot.PlotCanvas(self)
00086 
00087         #define the function for drawing pointLabels
00088         self.client.SetPointLabelFunc(self.DrawPointLabel)
00089 
00090         # Create mouse event for showing cursor coords in status bar
00091         self.client.canvas.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
00092 
00093         # Show closest point when enabled
00094         self.client.canvas.Bind(wx.EVT_MOTION, self.OnMotion)
00095 
00096         self.plotlist = []      # list of things to plot
00097         self.plot = None        # plot draw object
00098         self.ptitle = ""        # title of window
00099         self.xlabel = ""        # default X-axis label
00100         self.ylabel = ""        # default Y-axis label
00101 
00102         #
00103         # Bind various events
00104         #
00105         self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
00106         
00107         self.CentreOnScreen()
00108         
00109         self._createColorDict()
00110 
00111 
00112     def _createColorDict(self):
00113         """!Create color dictionary to return wx.Color tuples
00114         for assigning colors to images in imagery groups"""
00115                 
00116         self.colorDict = {}
00117         for clr in grass.named_colors.iterkeys():
00118             if clr == 'white' or clr == 'black': continue
00119             r = grass.named_colors[clr][0] * 255
00120             g = grass.named_colors[clr][1] * 255
00121             b = grass.named_colors[clr][2] * 255
00122             self.colorDict[clr] = (r,g,b,255)
00123 
00124     def InitPlotOpts(self, plottype):
00125         """!Initialize options for entire plot
00126         """
00127         
00128         self.plottype = plottype                # histogram, profile, or scatter
00129 
00130         self.properties = {}                    # plot properties
00131         self.properties['font'] = {}
00132         self.properties['font']['prop'] = UserSettings.Get(group = self.plottype, key = 'font')
00133         self.properties['font']['wxfont'] = wx.Font(11, wx.FONTFAMILY_SWISS,
00134                                                     wx.FONTSTYLE_NORMAL,
00135                                                     wx.FONTWEIGHT_NORMAL)
00136         
00137         if self.plottype == 'profile':
00138             self.properties['marker'] = UserSettings.Get(group = self.plottype, key = 'marker')
00139             # changing color string to tuple for markers/points
00140             colstr = str(self.properties['marker']['color'])
00141             self.properties['marker']['color'] = tuple(int(colval) for colval in colstr.strip('()').split(','))
00142 
00143         self.properties['grid'] = UserSettings.Get(group = self.plottype, key = 'grid')        
00144         colstr = str(self.properties['grid']['color']) # changing color string to tuple        
00145         self.properties['grid']['color'] = tuple(int(colval) for colval in colstr.strip('()').split(','))
00146                 
00147         self.properties['x-axis'] = {}
00148         self.properties['x-axis']['prop'] = UserSettings.Get(group = self.plottype, key = 'x-axis')
00149         self.properties['x-axis']['axis'] = None
00150 
00151         self.properties['y-axis'] = {}
00152         self.properties['y-axis']['prop'] = UserSettings.Get(group = self.plottype, key = 'y-axis')
00153         self.properties['y-axis']['axis'] = None
00154         
00155         self.properties['legend'] = UserSettings.Get(group = self.plottype, key = 'legend')
00156 
00157         self.zoom = False  # zooming disabled
00158         self.drag = False  # draging disabled
00159         self.client.SetShowScrollbars(True) # vertical and horizontal scrollbars
00160 
00161         # x and y axis set to normal (non-log)
00162         self.client.setLogScale((False, False))
00163         if self.properties['x-axis']['prop']['type']:
00164             self.client.SetXSpec(self.properties['x-axis']['prop']['type'])
00165         else:
00166             self.client.SetXSpec('auto')
00167         
00168         if self.properties['y-axis']['prop']['type']:
00169             self.client.SetYSpec(self.properties['y-axis']['prop']['type'])
00170         else:
00171             self.client.SetYSpec('auto')
00172         
00173     def InitRasterOpts(self, rasterList, plottype):
00174         """!Initialize or update raster dictionary for plotting
00175         """
00176 
00177         rdict = {} # initialize a dictionary
00178 
00179         for r in rasterList:
00180             idx = rasterList.index(r)
00181             
00182             try:
00183                 ret = grass.raster_info(r)
00184             except:
00185                 continue
00186                 # if r.info cannot parse map, skip it
00187                
00188             self.raster[r] = UserSettings.Get(group = plottype, key = 'raster') # some default settings
00189             rdict[r] = {} # initialize sub-dictionaries for each raster in the list
00190 
00191             
00192             rdict[r]['units'] = ''
00193             if ret['units'] not in ('(none)', '"none"', '', None):
00194                 rdict[r]['units'] = ret['units']
00195             
00196             rdict[r]['plegend'] = r.split('@')[0]
00197             rdict[r]['datalist'] = [] # list of cell value,frequency pairs for plotting histogram
00198             rdict[r]['pline'] = None
00199             rdict[r]['datatype'] = ret['datatype']
00200             rdict[r]['pwidth'] = 1
00201             rdict[r]['pstyle'] = 'solid'
00202             
00203             if idx <= len(self.colorList):
00204                 rdict[r]['pcolor'] = self.colorDict[self.colorList[idx]]
00205             else:
00206                 r = randint(0, 255)
00207                 b = randint(0, 255)
00208                 g = randint(0, 255)
00209                 rdict[r]['pcolor'] = ((r,g,b,255))
00210         
00211         return rdict
00212             
00213     def InitRasterPairs(self, rasterList, plottype):
00214         """!Initialize or update raster dictionary with raster pairs for
00215             bivariate scatterplots
00216         """
00217         
00218         if len(rasterList) == 0: return
00219 
00220         rdict = {} # initialize a dictionary
00221         for rpair in rasterList:
00222             idx = rasterList.index(rpair)
00223             
00224             try:
00225                 ret0 = grass.raster_info(rpair[0])
00226                 ret1 = grass.raster_info(rpair[1])
00227 
00228             except:
00229                 continue
00230                 # if r.info cannot parse map, skip it
00231 
00232             self.raster[rpair] = UserSettings.Get(group = plottype, key = 'rasters') # some default settings
00233             rdict[rpair] = {} # initialize sub-dictionaries for each raster in the list
00234             rdict[rpair][0] = {}
00235             rdict[rpair][1] = {}
00236             rdict[rpair][0]['units'] = ''
00237             rdict[rpair][1]['units'] = ''
00238 
00239             if ret0['units'] not in ('(none)', '"none"', '', None):
00240                 rdict[rpair][0]['units'] = ret0['units']
00241             if ret1['units'] not in ('(none)', '"none"', '', None):
00242                 rdict[rpair][1]['units'] = ret1['units']
00243                 
00244             rdict[rpair]['plegend'] = rpair[0].split('@')[0] + ' vs ' + rpair[1].split('@')[0]
00245             rdict[rpair]['datalist'] = [] # list of cell value,frequency pairs for plotting histogram
00246             rdict[rpair]['ptype'] = 'dot'
00247             rdict[rpair][0]['datatype'] = ret0['datatype']
00248             rdict[rpair][1]['datatype'] = ret1['datatype']
00249             rdict[rpair]['psize'] = 1
00250             rdict[rpair]['pfill'] = 'solid'
00251             
00252             if idx <= len(self.colorList):
00253                 rdict[rpair]['pcolor'] = self.colorDict[self.colorList[idx]]
00254             else:
00255                 r = randint(0, 255)
00256                 b = randint(0, 255)
00257                 g = randint(0, 255)
00258                 rdict[rpair]['pcolor'] = ((r,g,b,255))
00259             
00260         return rdict
00261 
00262     def SetGraphStyle(self):
00263         """!Set plot and text options
00264         """
00265         self.client.SetFont(self.properties['font']['wxfont'])
00266         self.client.SetFontSizeTitle(self.properties['font']['prop']['titleSize'])
00267         self.client.SetFontSizeAxis(self.properties['font']['prop']['axisSize'])
00268 
00269         self.client.SetEnableZoom(self.zoom)
00270         self.client.SetEnableDrag(self.drag)
00271         
00272         #
00273         # axis settings
00274         #
00275         if self.properties['x-axis']['prop']['type'] == 'custom':
00276             self.client.SetXSpec('min')
00277         else:
00278             self.client.SetXSpec(self.properties['x-axis']['prop']['type'])
00279 
00280         if self.properties['y-axis']['prop']['type'] == 'custom':
00281             self.client.SetYSpec('min')
00282         else:
00283             self.client.SetYSpec(self.properties['y-axis']['prop'])
00284 
00285         if self.properties['x-axis']['prop']['type'] == 'custom' and \
00286                self.properties['x-axis']['prop']['min'] < self.properties['x-axis']['prop']['max']:
00287             self.properties['x-axis']['axis'] = (self.properties['x-axis']['prop']['min'],
00288                                                  self.properties['x-axis']['prop']['max'])
00289         else:
00290             self.properties['x-axis']['axis'] = None
00291 
00292         if self.properties['y-axis']['prop']['type'] == 'custom' and \
00293                 self.properties['y-axis']['prop']['min'] < self.properties['y-axis']['prop']['max']:
00294             self.properties['y-axis']['axis'] = (self.properties['y-axis']['prop']['min'],
00295                                                  self.properties['y-axis']['prop']['max'])
00296         else:
00297             self.properties['y-axis']['axis'] = None
00298 
00299         self.client.SetEnableGrid(self.properties['grid']['enabled'])
00300         
00301         self.client.SetGridColour(wx.Color(self.properties['grid']['color'][0],
00302                                            self.properties['grid']['color'][1],
00303                                            self.properties['grid']['color'][2],
00304                                            255))
00305 
00306         self.client.SetFontSizeLegend(self.properties['font']['prop']['legendSize'])
00307         self.client.SetEnableLegend(self.properties['legend']['enabled'])
00308 
00309         if self.properties['x-axis']['prop']['log'] == True:
00310             self.properties['x-axis']['axis'] = None
00311             self.client.SetXSpec('min')
00312         if self.properties['y-axis']['prop']['log'] == True:
00313             self.properties['y-axis']['axis'] = None
00314             self.client.SetYSpec('min')
00315             
00316         self.client.setLogScale((self.properties['x-axis']['prop']['log'],
00317                                  self.properties['y-axis']['prop']['log']))
00318 
00319     def DrawPlot(self, plotlist):
00320         """!Draw line and point plot from list plot elements.
00321         """
00322         self.plot = plot.PlotGraphics(plotlist,
00323                                          self.ptitle,
00324                                          self.xlabel,
00325                                          self.ylabel)
00326 
00327         if self.properties['x-axis']['prop']['type'] == 'custom':
00328             self.client.SetXSpec('min')
00329         else:
00330             self.client.SetXSpec(self.properties['x-axis']['prop']['type'])
00331 
00332         if self.properties['y-axis']['prop']['type'] == 'custom':
00333             self.client.SetYSpec('min')
00334         else:
00335             self.client.SetYSpec(self.properties['y-axis']['prop']['type'])
00336 
00337         self.client.Draw(self.plot, self.properties['x-axis']['axis'],
00338                          self.properties['y-axis']['axis'])
00339                 
00340     def DrawPointLabel(self, dc, mDataDict):
00341         """!This is the fuction that defines how the pointLabels are
00342             plotted dc - DC that will be passed mDataDict - Dictionary
00343             of data that you want to use for the pointLabel
00344 
00345             As an example I have decided I want a box at the curve
00346             point with some text information about the curve plotted
00347             below.  Any wxDC method can be used.
00348         """
00349         dc.SetPen(wx.Pen(wx.BLACK))
00350         dc.SetBrush(wx.Brush( wx.BLACK, wx.SOLID ) )
00351 
00352         sx, sy = mDataDict["scaledXY"] #scaled x,y of closest point
00353         dc.DrawRectangle( sx-5,sy-5, 10, 10)  #10by10 square centered on point
00354         px,py = mDataDict["pointXY"]
00355         cNum = mDataDict["curveNum"]
00356         pntIn = mDataDict["pIndex"]
00357         legend = mDataDict["legend"]
00358         #make a string to display
00359         s = "Crv# %i, '%s', Pt. (%.2f,%.2f), PtInd %i" %(cNum, legend, px, py, pntIn)
00360         dc.DrawText(s, sx , sy+1)
00361 
00362     def OnZoom(self, event):
00363         """!Enable zooming and disable dragging
00364         """
00365         self.zoom = True
00366         self.drag = False
00367         self.client.SetEnableZoom(self.zoom)
00368         self.client.SetEnableDrag(self.drag)
00369 
00370     def OnDrag(self, event):
00371         """!Enable dragging and disable zooming
00372         """
00373         self.zoom = False
00374         self.drag = True
00375         self.client.SetEnableDrag(self.drag)
00376         self.client.SetEnableZoom(self.zoom)
00377 
00378     def OnRedraw(self, event):
00379         """!Redraw the plot window. Unzoom to original size
00380         """
00381         self.client.Reset()
00382         self.client.Redraw()
00383        
00384     def OnErase(self, event):
00385         """!Erase the plot window
00386         """
00387         self.client.Clear()
00388         self.mapwin.ClearLines(self.mapwin.pdc)
00389         self.mapwin.ClearLines(self.mapwin.pdcTmp)
00390         self.mapwin.polycoords = []
00391         self.mapwin.Refresh()
00392 
00393     def SaveToFile(self, event):
00394         """!Save plot to graphics file
00395         """
00396         self.client.SaveFile()
00397 
00398     def OnMouseLeftDown(self,event):
00399         self.SetStatusText(_("Left Mouse Down at Point:") + \
00400                                " (%.4f, %.4f)" % self.client._getXY(event))
00401         event.Skip() # allows plotCanvas OnMouseLeftDown to be called
00402 
00403     def OnMotion(self, event):
00404         """!Indicate when mouse is outside the plot area
00405         """
00406         if self.client.OnLeave(event): print 'out of area'
00407         #show closest point (when enbled)
00408         if self.client.GetEnablePointLabel() == True:
00409             #make up dict with info for the pointLabel
00410             #I've decided to mark the closest point on the closest curve
00411             dlst =  self.client.GetClosetPoint( self.client._getXY(event), pointScaled =  True)
00412             if dlst != []:      #returns [] if none
00413                 curveNum, legend, pIndex, pointXY, scaledXY, distance = dlst
00414                 #make up dictionary to pass to my user function (see DrawPointLabel)
00415                 mDataDict =  {"curveNum":curveNum, "legend":legend, "pIndex":pIndex,\
00416                     "pointXY":pointXY, "scaledXY":scaledXY}
00417                 #pass dict to update the pointLabel
00418                 self.client.UpdatePointLabel(mDataDict)
00419         event.Skip()           #go to next handler        
00420  
00421  
00422     def PlotOptionsMenu(self, event):
00423         """!Popup menu for plot and text options
00424         """
00425         point = wx.GetMousePosition()
00426         popt = wx.Menu()
00427         # Add items to the menu
00428         settext = wx.MenuItem(popt, wx.ID_ANY, _('Text settings'))
00429         popt.AppendItem(settext)
00430         self.Bind(wx.EVT_MENU, self.PlotText, settext)
00431 
00432         setgrid = wx.MenuItem(popt, wx.ID_ANY, _('Plot settings'))
00433         popt.AppendItem(setgrid)
00434         self.Bind(wx.EVT_MENU, self.PlotOptions, setgrid)
00435 
00436         # Popup the menu.  If an item is selected then its handler
00437         # will be called before PopupMenu returns.
00438         self.PopupMenu(popt)
00439         popt.Destroy()
00440 
00441     def NotFunctional(self):
00442         """!Creates a 'not functional' message dialog
00443         """
00444         dlg = wx.MessageDialog(parent = self,
00445                                message = _('This feature is not yet functional'),
00446                                caption = _('Under Construction'),
00447                                style = wx.OK | wx.ICON_INFORMATION)
00448         dlg.ShowModal()
00449         dlg.Destroy()
00450 
00451     def OnPlotText(self, dlg):
00452         """!Custom text settings for histogram plot.
00453         """
00454         self.ptitle = dlg.ptitle
00455         self.xlabel = dlg.xlabel
00456         self.ylabel = dlg.ylabel
00457         dlg.UpdateSettings()
00458 
00459         self.client.SetFont(self.properties['font']['wxfont'])
00460         self.client.SetFontSizeTitle(self.properties['font']['prop']['titleSize'])
00461         self.client.SetFontSizeAxis(self.properties['font']['prop']['axisSize'])
00462 
00463         if self.plot:
00464             self.plot.setTitle(dlg.ptitle)
00465             self.plot.setXLabel(dlg.xlabel)
00466             self.plot.setYLabel(dlg.ylabel)
00467         
00468         self.OnRedraw(event = None)
00469     
00470     def PlotText(self, event):
00471         """!Set custom text values for profile title and axis labels.
00472         """
00473         dlg = TextDialog(parent = self, id = wx.ID_ANY, 
00474                                  plottype = self.plottype, 
00475                                  title = _('Histogram text settings'))
00476 
00477         if dlg.ShowModal() == wx.ID_OK:
00478             self.OnPlotText(dlg)
00479 
00480         dlg.Destroy()
00481 
00482     def PlotOptions(self, event):
00483         """!Set various profile options, including: line width, color,
00484         style; marker size, color, fill, and style; grid and legend
00485         options.  Calls OptDialog class.
00486         """
00487         dlg = OptDialog(parent = self, id = wx.ID_ANY, 
00488                         plottype = self.plottype, 
00489                         title = _('Plot settings'))
00490         btnval = dlg.ShowModal()
00491 
00492         if btnval == wx.ID_SAVE:
00493             dlg.UpdateSettings()            
00494             self.SetGraphStyle()            
00495             dlg.Destroy()            
00496         elif btnval == wx.ID_CANCEL:
00497             dlg.Destroy()
00498 
00499     def PrintMenu(self, event):
00500         """!Print options and output menu
00501         """
00502         point = wx.GetMousePosition()
00503         printmenu = wx.Menu()
00504         for title, handler in ((_("Page setup"), self.OnPageSetup),
00505                                (_("Print preview"), self.OnPrintPreview),
00506                                (_("Print display"), self.OnDoPrint)):
00507             item = wx.MenuItem(printmenu, wx.ID_ANY, title)
00508             printmenu.AppendItem(item)
00509             self.Bind(wx.EVT_MENU, handler, item)
00510         
00511         # Popup the menu.  If an item is selected then its handler
00512         # will be called before PopupMenu returns.
00513         self.PopupMenu(printmenu)
00514         printmenu.Destroy()
00515 
00516     def OnPageSetup(self, event):
00517         self.client.PageSetup()
00518 
00519     def OnPrintPreview(self, event):
00520         self.client.PrintPreview()
00521 
00522     def OnDoPrint(self, event):
00523         self.client.Printout()
00524 
00525     def OnQuit(self, event):
00526         self.Close(True)
00527 
00528     def OnCloseWindow(self, event):
00529         """!Close plot window and clean up
00530         """
00531         try:
00532             self.mapwin.ClearLines()
00533             self.mapwin.mouse['begin'] = self.mapwin.mouse['end'] = (0.0, 0.0)
00534             self.mapwin.mouse['use'] = 'pointer'
00535             self.mapwin.mouse['box'] = 'point'
00536             self.mapwin.polycoords = []
00537             self.mapwin.UpdateMap(render = False, renderVector = False)
00538         except:
00539             pass
00540         
00541         self.mapwin.SetCursor(self.Parent.cursors["default"])
00542         self.Destroy()
00543