|
GRASS Programmer's Manual
6.5.svn(2012)-r51648
|
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